1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
use reqwest::Client;

use crate::api::data::{Error as DataError, OwnedData};
use crate::api::nonce::{request_nonce, NonceError};
use crate::api::request::{ensure_success, ResponseError};
use crate::api::url::UrlBuilder;
use crate::file::remote_file::RemoteFile;

/// An action to delete a remote file.
///
/// This API specification for this action is compatible with both Firefox Send v2 and v3.
pub struct Delete<'a> {
    /// The remote file to delete.
    file: &'a RemoteFile,

    /// The authentication nonce.
    /// May be an empty vector if the nonce is unknown.
    nonce: Vec<u8>,
}

impl<'a> Delete<'a> {
    /// Construct a new delete action for the given file.
    pub fn new(file: &'a RemoteFile, nonce: Option<Vec<u8>>) -> Self {
        Self {
            file,
            nonce: nonce.unwrap_or_default(),
        }
    }

    /// Invoke the delete action.
    pub fn invoke(mut self, client: &Client) -> Result<(), Error> {
        // Fetch the authentication nonce if not set yet
        if self.nonce.is_empty() {
            self.nonce = self.fetch_auth_nonce(client)?;
        }

        // Create owned data, to send to the server for authentication
        let data = OwnedData::from(DeleteData::new(), &self.file)
            .map_err(|err| PrepareError::DeleteData(DeleteDataError::Owned(err)))?;

        // Send the delete request
        self.request_delete(client, &data)
    }

    /// Fetch the authentication nonce for the file from the remote server.
    fn fetch_auth_nonce(&self, client: &Client) -> Result<Vec<u8>, Error> {
        request_nonce(client, UrlBuilder::download(self.file, false)).map_err(|err| err.into())
    }

    /// Send a request to delete the remote file, with the given data.
    fn request_delete(&self, client: &Client, data: &OwnedData<DeleteData>) -> Result<(), Error> {
        // Get the delete URL, and send the request
        let url = UrlBuilder::api_delete(self.file);
        let response = client
            .post(url)
            .json(&data)
            .send()
            .map_err(|_| DeleteError::Request)?;

        // Ensure the status code is successful
        ensure_success(&response).map_err(|err| err.into())
    }
}

/// The delete data object.
/// This object is currently empty, as no additional data is sent to the
/// server.
#[derive(Debug, Serialize, Default)]
pub struct DeleteData {}

impl DeleteData {
    /// Constructor.
    pub fn new() -> Self {
        DeleteData::default()
    }
}

#[derive(Fail, Debug)]
pub enum Error {
    /// An error occurred while preparing the action.
    #[fail(display = "failed to prepare the action")]
    Prepare(#[cause] PrepareError),

    /// The given Send file has expired, or did never exist in the first place.
    /// Therefore the file could not be downloaded.
    #[fail(display = "the file has expired or did never exist")]
    Expired,

    /// An error has occurred while sending the file deletion request.
    #[fail(display = "failed to send the file deletion request")]
    Delete(#[cause] DeleteError),
}

impl From<NonceError> for Error {
    fn from(err: NonceError) -> Error {
        match err {
            NonceError::Expired => Error::Expired,
            err => Error::Prepare(PrepareError::Auth(err)),
        }
    }
}

impl From<PrepareError> for Error {
    fn from(err: PrepareError) -> Error {
        Error::Prepare(err)
    }
}

impl From<DeleteError> for Error {
    fn from(err: DeleteError) -> Error {
        Error::Delete(err)
    }
}

#[derive(Debug, Fail)]
pub enum DeleteDataError {
    /// Some error occurred while trying to wrap the deletion data in an
    /// owned object, which is required for authentication on the server.
    /// The wrapped error further described the problem.
    #[fail(display = "")]
    Owned(#[cause] DataError),
}

#[derive(Fail, Debug)]
pub enum PrepareError {
    /// Failed to authenticate
    #[fail(display = "failed to authenticate")]
    Auth(#[cause] NonceError),

    /// An error occurred while building the deletion data that will be
    /// send to the server.
    #[fail(display = "invalid parameters")]
    DeleteData(#[cause] DeleteDataError),
}

#[derive(Fail, Debug)]
pub enum DeleteError {
    /// Sending the file deletion request failed.
    #[fail(display = "failed to send file deletion request")]
    Request,

    /// The server responded with an error while requesting file deletion.
    #[fail(display = "bad response from server while deleting file")]
    Response(#[cause] ResponseError),
}

impl From<ResponseError> for Error {
    fn from(err: ResponseError) -> Self {
        match err {
            ResponseError::Expired => Error::Expired,
            err => Error::Delete(DeleteError::Response(err)),
        }
    }
}