ffsend_api/action/
metadata.rs

1use reqwest::header::AUTHORIZATION;
2use serde::{
3    de::{Error as SerdeError, Unexpected},
4    Deserialize, Deserializer,
5};
6use serde_json::{self, Value as JsonValue};
7use thiserror::Error;
8
9use super::exists::{Error as ExistsError, Exists as ExistsAction};
10use crate::api::nonce::{header_nonce, request_nonce, NonceError};
11use crate::api::request::{ensure_success, ResponseError};
12use crate::api::url::UrlBuilder;
13use crate::client::Client;
14use crate::crypto::key_set::KeySet;
15use crate::crypto::sig::signature_encoded;
16use crate::crypto::{self, api::MetadataError};
17use crate::file::metadata::Metadata as MetadataData;
18use crate::file::remote_file::RemoteFile;
19
20/// An action to fetch file metadata.
21///
22/// This API specification for this action is compatible with both Firefox Send v2 and v3.
23/// Depending on the server API version, a different metadata enum variant is returned.
24pub struct Metadata<'a> {
25    /// The remote file to fetch the metadata for.
26    file: &'a RemoteFile,
27
28    /// An optional password to decrypt a protected file.
29    password: Option<String>,
30
31    /// Check whether the file exists (recommended).
32    check_exists: bool,
33}
34
35impl<'a> Metadata<'a> {
36    /// Construct a new metadata action.
37    pub fn new(file: &'a RemoteFile, password: Option<String>, check_exists: bool) -> Self {
38        Self {
39            file,
40            password,
41            check_exists,
42        }
43    }
44
45    /// Invoke the metadata action.
46    pub fn invoke(self, client: &Client) -> Result<MetadataResponse, Error> {
47        // Make sure the given file exists
48        if self.check_exists {
49            let exist_response = ExistsAction::new(&self.file).invoke(&client)?;
50
51            // Return an error if the file does not exist
52            if !exist_response.exists() {
53                return Err(Error::Expired);
54            }
55
56            // Make sure a password is given when it is required
57            if self.password.is_none() && exist_response.requires_password() {
58                return Err(Error::PasswordRequired);
59            }
60        }
61
62        // Create a key set for the file
63        let key = KeySet::from(self.file, self.password.as_ref());
64
65        // Fetch the authentication nonce
66        let auth_nonce = self.fetch_auth_nonce(client)?;
67
68        // Fetch the metadata and the metadata nonce, return the result
69        self.fetch_metadata(&client, &key, &auth_nonce)
70            .map_err(|err| err.into())
71    }
72
73    /// Fetch the authentication nonce for the file from the remote server.
74    fn fetch_auth_nonce(&self, client: &Client) -> Result<Vec<u8>, Error> {
75        request_nonce(client, UrlBuilder::download(self.file, false)).map_err(|err| err.into())
76    }
77
78    /// Create a metadata nonce, and fetch the metadata for the file from the
79    /// Send server.
80    ///
81    /// The key set, along with the authentication nonce must be given.
82    ///
83    /// The metadata, with the meta nonce is returned.
84    fn fetch_metadata(
85        &self,
86        client: &Client,
87        key: &KeySet,
88        auth_nonce: &[u8],
89    ) -> Result<MetadataResponse, MetaError> {
90        // Compute the cryptographic signature for authentication
91        let sig = signature_encoded(key.auth_key().unwrap(), &auth_nonce)
92            .map_err(|_| MetaError::ComputeSignature)?;
93
94        // Build the request, fetch the encrypted metadata
95        let response = client
96            .get(UrlBuilder::api_metadata(self.file))
97            .header(AUTHORIZATION.as_str(), format!("send-v1 {}", sig))
98            .send()
99            .map_err(|_| MetaError::NonceRequest)?;
100
101        // Ensure the status code is successful
102        ensure_success(&response).map_err(MetaError::NonceResponse)?;
103
104        // Get the metadata nonce
105        let nonce = header_nonce(&response).map_err(MetaError::Nonce)?;
106
107        // Parse the metadata response
108        MetadataResponse::from(
109            &response
110                .json::<RawMetadataResponse>()
111                .map_err(|_| MetaError::Malformed)?,
112            &key,
113            nonce,
114        )
115        .map_err(|_| MetaError::Decrypt)
116    }
117}
118
119/// The metadata response from the server, when fetching the data through
120/// the API.
121/// This response contains raw metadata, which is still encrypted.
122// TODO: rename this into EncryptedMetadataResponse?
123// TODO: don't use enum, make size field optional
124#[derive(Debug, Deserialize)]
125#[serde(untagged)]
126pub enum RawMetadataResponse {
127    /// Raw metadata using in Send v2.
128    V2 {
129        /// The encrypted metadata.
130        #[serde(rename = "metadata")]
131        meta: String,
132
133        /// The file size in bytes.
134        #[serde(deserialize_with = "deserialize_u64")]
135        size: u64,
136    },
137
138    /// Raw metadata using in Send v3.
139    V3 {
140        /// The encrypted metadata.
141        #[serde(rename = "metadata")]
142        meta: String,
143    },
144    // TODO: Use `finalDownload` field?
145    // TODO: Use `ttl` field?
146}
147
148impl RawMetadataResponse {
149    /// Get and decrypt the metadata, based on the raw data in this response.
150    ///
151    /// The decrypted data is verified using an included tag.
152    /// If verification failed, an error is returned.
153    pub fn decrypt_metadata(&self, key_set: &KeySet) -> Result<MetadataData, MetadataError> {
154        crypto::api::decrypt_metadata(self.meta(), key_set)
155    }
156
157    /// Get the encrypted metadata.
158    fn meta(&self) -> &str {
159        match self {
160            RawMetadataResponse::V2 { meta, .. } => &meta,
161            RawMetadataResponse::V3 { meta } => &meta,
162        }
163    }
164
165    /// Get the file size in bytes, if provided by the server (`= Send v2`).
166    // TODO: use proper size
167    // pub fn size(&self) -> Option<u64> {
168    //     self.size
169    // }
170    pub fn size(&self) -> Option<u64> {
171        match self {
172            RawMetadataResponse::V2 { size, .. } => Some(*size),
173            RawMetadataResponse::V3 { .. } => None,
174        }
175    }
176}
177
178/// A more forgiving deserializer for u64 values than the strict default.
179/// This is used when deserializing raw metadata,
180/// as the file size u64 is formatted as a string.
181fn deserialize_u64<'d, D>(d: D) -> Result<u64, D::Error>
182where
183    D: Deserializer<'d>,
184{
185    Deserialize::deserialize(d).and_then(|value: JsonValue| match value {
186        JsonValue::Number(n) => n.as_u64().ok_or_else(|| {
187            if let Some(n) = n.as_i64() {
188                SerdeError::invalid_type(Unexpected::Signed(n), &"a positive integer")
189            } else if let Some(n) = n.as_f64() {
190                SerdeError::invalid_type(Unexpected::Float(n), &"a positive integer")
191            } else {
192                SerdeError::invalid_type(Unexpected::Str(&n.to_string()), &"a positive integer")
193            }
194        }),
195        JsonValue::String(s) => s
196            .parse()
197            .map_err(|_| SerdeError::invalid_type(Unexpected::Str(&s), &"a positive integer")),
198        o => Err(SerdeError::invalid_type(
199            Unexpected::Other(&o.to_string()),
200            &"a positive integer",
201        )),
202    })
203}
204
205/// The decoded and decrypted metadata response, holding all the properties.
206/// This response object is returned from this action.
207pub struct MetadataResponse {
208    /// The actual metadata.
209    metadata: MetadataData,
210
211    /// The file size in bytes, if provided by the server (`< send v3`).
212    size: Option<u64>,
213
214    /// The metadata nonce.
215    nonce: Vec<u8>,
216}
217
218impl<'a> MetadataResponse {
219    /// Construct a new response with the given metadata and nonce.
220    pub fn new(metadata: MetadataData, size: Option<u64>, nonce: Vec<u8>) -> Self {
221        MetadataResponse {
222            metadata,
223            size,
224            nonce,
225        }
226    }
227
228    // Construct a new metadata response from the given raw metadata response,
229    // with an additional key set and nonce.
230    //
231    // This internally decrypts the metadata from the raw response.
232    // An error is returned if decrypting the metadata failed.
233    pub fn from(
234        raw: &RawMetadataResponse,
235        key_set: &KeySet,
236        nonce: Vec<u8>,
237    ) -> Result<Self, MetadataError> {
238        Ok(Self::new(raw.decrypt_metadata(key_set)?, raw.size(), nonce))
239    }
240
241    /// Get the metadata.
242    pub fn metadata(&self) -> &MetadataData {
243        &self.metadata
244    }
245
246    /// Get the file size in bytes.
247    pub fn size(&self) -> u64 {
248        // TODO: return proper error if not set, should never happen with current API
249        self.size
250            .unwrap_or_else(|| self.metadata.size().expect("file size unknown, newer API?"))
251    }
252
253    /// Get the nonce.
254    pub fn nonce(&self) -> &Vec<u8> {
255        &self.nonce
256    }
257}
258
259#[derive(Error, Debug)]
260pub enum Error {
261    /// An error occurred while checking whether the file exists on the
262    /// server.
263    #[error("failed to check whether the file exists")]
264    Exists(#[from] ExistsError),
265
266    /// A general error occurred while requesting the file data.
267    /// This may be because authentication failed, because decrypting the
268    /// file metadata didn't succeed, or due to some other reason.
269    #[error("failed to request file data")]
270    Request(#[from] RequestError),
271
272    /// The given Send file has expired, or did never exist in the first place.
273    /// Therefore the file could not be downloaded.
274    #[error("the file has expired or did never exist")]
275    Expired,
276
277    /// A password is required, but was not given.
278    #[error("missing password, password required")]
279    PasswordRequired,
280}
281
282impl From<MetaError> for Error {
283    fn from(err: MetaError) -> Error {
284        Error::Request(RequestError::Meta(err))
285    }
286}
287
288impl From<NonceError> for Error {
289    fn from(err: NonceError) -> Error {
290        match err {
291            NonceError::Expired => Error::Expired,
292            err => Error::Request(RequestError::Auth(err)),
293        }
294    }
295}
296
297#[derive(Error, Debug)]
298pub enum RequestError {
299    /// Failed authenticating, in order to fetch the file data.
300    #[error("failed to authenticate")]
301    Auth(#[from] NonceError),
302
303    /// Failed to retrieve the file metadata.
304    #[error("failed to retrieve file metadata")]
305    Meta(#[from] MetaError),
306}
307
308#[derive(Error, Debug)]
309pub enum MetaError {
310    /// An error occurred while computing the cryptographic signature used for
311    /// decryption.
312    #[error("failed to compute cryptographic signature")]
313    ComputeSignature,
314
315    /// Sending the request to gather the metadata encryption nonce failed.
316    #[error("failed to request metadata nonce")]
317    NonceRequest,
318
319    /// The server responded with an error while fetching the metadata
320    /// encryption nonce.
321    #[error("bad response from server while fetching metadata nonce")]
322    NonceResponse(#[from] ResponseError),
323
324    /// Couldn't parse the metadata encryption nonce.
325    #[error("failed to parse the metadata encryption nonce")]
326    Nonce(#[from] NonceError),
327
328    /// The received metadata is malformed, and couldn't be decoded or
329    /// interpreted.
330    #[error("received malformed metadata")]
331    Malformed,
332
333    /// Failed to decrypt the received metadata.
334    #[error("failed to decrypt received metadata")]
335    Decrypt,
336}