ffsend_api/action/
params.rs

1use thiserror::Error;
2
3use crate::api::data::{Error as DataError, OwnedData};
4use crate::api::nonce::{request_nonce, NonceError};
5use crate::api::request::{ensure_success, ResponseError};
6use crate::api::url::UrlBuilder;
7use crate::api::Version;
8use crate::client::Client;
9use crate::file::remote_file::RemoteFile;
10
11/// The minimum allowed number of downloads, enforced by the server.
12// TODO: remove parameter, use from config
13pub const PARAMS_DOWNLOAD_MIN: u8 = 1;
14
15/// The maximum (inclusive) allowed number of downloads,
16/// enforced by the server.
17// TODO: remove parameter, use from config
18pub const PARAMS_DOWNLOAD_MAX: u8 = 20;
19
20/// An action to set parameters for a shared file.
21///
22/// This API specification for this action is compatible with both Firefox Send v2 and v3.
23pub struct Params<'a> {
24    /// The remote file to change the parameters for.
25    file: &'a RemoteFile,
26
27    /// The parameter data that is sent to the server.
28    params: ParamsData,
29
30    /// The authentication nonce.
31    /// May be an empty vector if the nonce is unknown.
32    nonce: Vec<u8>,
33}
34
35impl<'a> Params<'a> {
36    /// Construct a new parameters action for the given remote file.
37    pub fn new(file: &'a RemoteFile, params: ParamsData, nonce: Option<Vec<u8>>) -> Self {
38        Self {
39            file,
40            params,
41            nonce: nonce.unwrap_or_default(),
42        }
43    }
44
45    /// Invoke the parameters action.
46    pub fn invoke(mut self, client: &Client) -> Result<(), Error> {
47        // TODO: validate that the parameters object isn't empty
48
49        // Fetch the authentication nonce if not set yet
50        if self.nonce.is_empty() {
51            self.nonce = self.fetch_auth_nonce(client)?;
52        }
53
54        // Wrap the parameters data
55        let data = OwnedData::from(self.params.clone(), &self.file)
56            .map_err(|err| -> PrepareError { err.into() })?;
57
58        // Send the request to change the parameters
59        self.change_params(client, &data)
60    }
61
62    /// Fetch the authentication nonce for the file from the remote server.
63    fn fetch_auth_nonce(&self, client: &Client) -> Result<Vec<u8>, Error> {
64        request_nonce(client, UrlBuilder::download(self.file, false)).map_err(|err| err.into())
65    }
66
67    /// Send the request for changing the parameters.
68    fn change_params(&self, client: &Client, data: &OwnedData<ParamsData>) -> Result<(), Error> {
69        // Get the params URL, and send the change
70        let url = UrlBuilder::api_params(self.file);
71        let response = client
72            .post(url)
73            .json(&data)
74            .send()
75            .map_err(|_| ChangeError::Request)?;
76
77        // Ensure the response is successful
78        ensure_success(&response).map_err(|err| err.into())
79    }
80}
81
82/// The parameters data object, that is sent to the server.
83// TODO: make sure builder parameters are in-bound as well
84#[derive(Clone, Debug, Builder, Serialize)]
85pub struct ParamsData {
86    /// The number of times this file may be downloaded.
87    /// This value must be within a specific range, as enforced by Send servers.
88    #[serde(default)]
89    #[builder(default)]
90    pub download_limit: Option<u8>,
91
92    /// The time in seconds after when the file expires.
93    /// This value must be within a specific range, as enforced by Send servers.
94    /// Only used with Send v3.
95    #[serde(default)]
96    #[builder(default)]
97    #[serde(rename = "timeLimit")]
98    pub expiry_time: Option<usize>,
99}
100
101impl ParamsData {
102    /// Construct a new parameters object, that is empty.
103    pub fn new() -> Self {
104        Self::default()
105    }
106
107    /// Create a new parameters data object, with the given parameters.
108    // TODO: the values must be between bounds
109    pub fn from(download_limit: Option<u8>, expiry_time: Option<usize>) -> Self {
110        ParamsData {
111            download_limit,
112            expiry_time,
113        }
114    }
115
116    /// Set the maximum number of allowed downloads, after which the file
117    /// will be removed.
118    ///
119    /// `None` may be given, to keep this parameter as is.
120    ///
121    /// An error may be returned if the download value is out of the allowed
122    /// bound. These bounds are fixed and enforced by the server.
123    /// See `PARAMS_DOWNLOAD_MIN` and `PARAMS_DOWNLOAD_MAX`.
124    pub fn set_download_limit(
125        &mut self,
126        download_limit: Option<u8>,
127    ) -> Result<(), ParamsDataError> {
128        // TODO: use better bound check based on version
129
130        // Check the download limit bounds
131        if let Some(d) = download_limit {
132            if d < PARAMS_DOWNLOAD_MIN || d > PARAMS_DOWNLOAD_MAX {
133                return Err(ParamsDataError::DownloadBounds);
134            }
135        }
136
137        // Set the download limit
138        self.download_limit = download_limit;
139        Ok(())
140    }
141
142    /// Set the expiry time in seconds for files.
143    ///
144    /// `None` may be given, to keep this parameter as is.
145    ///
146    /// An error may be returned if the expiry time value is out of the allowed
147    /// bound. These bounds are fixed and enforced by the server.
148    #[cfg(feature = "send3")]
149    pub fn set_expiry_time(&mut self, expiry_time: Option<usize>) -> Result<(), ParamsDataError> {
150        // TODO: do proper bound check based on version
151
152        // // Check the download limit bounds
153        // if let Some(d) = expiry_time {
154        //     if d < PARAMS_DOWNLOAD_MIN || d > PARAMS_DOWNLOAD_MAX {
155        //         return Err(ParamsDataError::DownloadBounds);
156        //     }
157        // }
158
159        // Set the expiry time
160        self.expiry_time = expiry_time;
161        Ok(())
162    }
163
164    /// Check whether this parameters object is empty,
165    /// and wouldn't change any parameter on the server when sent.
166    /// Sending an empty parameter data object would thus be useless.
167    pub fn is_empty(&self) -> bool {
168        self.download_limit.is_none() && self.expiry_time.is_none()
169    }
170
171    /// Normalize this data for the given version.
172    ///
173    /// For example, Send v2 does not support the expiry time field. If normalizing for this
174    /// version this field is dropped from the struct.
175    #[allow(unused_variables)]
176    pub fn normalize(&mut self, version: Version) {
177        #[cfg(feature = "send2")]
178        {
179            #[allow(unreachable_patterns)]
180            match version {
181                Version::V2 => self.expiry_time = None,
182                _ => {}
183            }
184        }
185    }
186}
187
188impl Default for ParamsData {
189    fn default() -> ParamsData {
190        ParamsData {
191            download_limit: None,
192            expiry_time: None,
193        }
194    }
195}
196
197#[derive(Error, Debug)]
198pub enum Error {
199    /// An error occurred while preparing the action.
200    #[error("failed to prepare setting the parameters")]
201    Prepare(#[from] PrepareError),
202
203    /// The given Send file has expired, or did never exist in the first place.
204    /// Therefore the file could not be downloaded.
205    #[error("the file has expired or did never exist")]
206    Expired,
207
208    /// An error has occurred while sending the parameter change request to
209    /// the server.
210    #[error("failed to send the parameter change request")]
211    Change(#[from] ChangeError),
212}
213
214impl From<NonceError> for Error {
215    fn from(err: NonceError) -> Error {
216        match err {
217            NonceError::Expired => Error::Expired,
218            err => Error::Prepare(PrepareError::Auth(err)),
219        }
220    }
221}
222
223impl From<ResponseError> for Error {
224    fn from(err: ResponseError) -> Error {
225        match err {
226            ResponseError::Expired => Error::Expired,
227            err => Error::Change(ChangeError::Response(err)),
228        }
229    }
230}
231
232#[derive(Debug, Error)]
233pub enum ParamsDataError {
234    /// The number of downloads is invalid, as it was out of the allowed
235    /// bounds. See `PARAMS_DOWNLOAD_MIN` and `PARAMS_DOWNLOAD_MAX`.
236    // TODO: use bound values from constants, don't hardcode them here
237    #[error("invalid number of downloads, must be between 1 and 20")]
238    DownloadBounds,
239
240    /// Some error occurred while trying to wrap the parameter data in an
241    /// owned object, which is required for authentication on the server.
242    /// The wrapped error further described the problem.
243    #[error("")]
244    Owned(#[from] DataError),
245}
246
247#[derive(Error, Debug)]
248pub enum PrepareError {
249    /// Failed authenticating, needed to change the parameters.
250    #[error("failed to authenticate")]
251    Auth(#[from] NonceError),
252
253    /// An error occurred while building the parameter data that will be send
254    /// to the server.
255    #[error("invalid parameters")]
256    ParamsData(#[from] ParamsDataError),
257}
258
259impl From<DataError> for PrepareError {
260    fn from(err: DataError) -> PrepareError {
261        PrepareError::ParamsData(ParamsDataError::Owned(err))
262    }
263}
264
265#[derive(Error, Debug)]
266pub enum ChangeError {
267    /// Sending the request to change the parameters failed.
268    #[error("failed to send parameter change request")]
269    Request,
270
271    /// The server responded with an error while changing the file parameters.
272    #[error("bad response from server while changing parameters")]
273    Response(#[from] ResponseError),
274}