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}