mega/
lib.rs

1mod client;
2#[cfg(feature = "easy")]
3mod easy;
4mod file_validator;
5mod parsed_mega_url;
6mod types;
7
8pub use self::client::Client;
9#[cfg(feature = "easy")]
10pub use self::easy::Client as EasyClient;
11#[cfg(feature = "easy")]
12pub use self::easy::FileDownloadReader as EasyFileDownloadReader;
13#[cfg(feature = "easy")]
14pub use self::easy::GetAttributesBuilder as EasyGetAttributesBuilder;
15pub use self::file_validator::FileValidationError;
16pub use self::file_validator::FileValidator;
17pub use self::parsed_mega_url::ParseMegaUrlError;
18pub use self::parsed_mega_url::ParsedMegaFileUrl;
19pub use self::parsed_mega_url::ParsedMegaFolderUrl;
20pub use self::parsed_mega_url::ParsedMegaUrl;
21pub use self::types::Command;
22pub use self::types::DecodeAttributesError;
23pub use self::types::ErrorCode;
24pub use self::types::FetchNodesNodeKind;
25pub use self::types::FetchNodesResponse;
26pub use self::types::FileKey;
27pub use self::types::FileKeyParseError;
28pub use self::types::FileOrFolderKey;
29pub use self::types::FolderKey;
30pub use self::types::FolderKeyParseError;
31pub use self::types::GetAttributesResponse;
32pub use self::types::Response;
33pub use self::types::ResponseData;
34pub use url::Url;
35
36/// The library error type
37#[derive(Debug, thiserror::Error)]
38pub enum Error {
39    /// A reqwest Error
40    #[error("http error")]
41    Reqwest(#[from] reqwest::Error),
42
43    /// A Url Error
44    #[error("url error")]
45    Url(#[from] url::ParseError),
46
47    /// The returned number of responses did not match what was expected
48    #[error("expected \"{expected}\" responses, but got \"{actual}\"")]
49    ResponseLengthMismatch { expected: usize, actual: usize },
50
51    /// There was an api error
52    #[error("api error")]
53    ApiError(#[from] ErrorCode),
54
55    /// Failed to decode attributes
56    #[error("failed to decode attributes")]
57    DecodeAttributes(#[from] DecodeAttributesError),
58
59    #[cfg(feature = "easy")]
60    #[error("channel closed without response")]
61    NoResponse,
62
63    #[cfg(feature = "easy")]
64    #[error("error occured as part of a batched send")]
65    BatchSend(self::easy::ArcError<Self>),
66
67    #[cfg(feature = "easy")]
68    #[error("unexpected response data type")]
69    UnexpectedResponseDataType,
70}
71
72#[cfg(test)]
73mod test {
74    use super::*;
75    use cbc::cipher::KeyIvInit;
76    use cbc::cipher::StreamCipher;
77
78    type Aes128Ctr128BE = ctr::Ctr128BE<aes::Aes128>;
79
80    pub const TEST_FILE: &str =
81        "https://mega.nz/file/7glwEQBT#Fy9cwPpCmuaVdEkW19qwBLaiMeyufB1kseqisOAxfi8";
82    pub const TEST_FILE_KEY: &str = "Fy9cwPpCmuaVdEkW19qwBLaiMeyufB1kseqisOAxfi8";
83    pub const TEST_FILE_ID: &str = "7glwEQBT";
84
85    pub const TEST_FOLDER: &str = "https://mega.nz/folder/MWsm3aBL#xsXXTpoYEFDRQdeHPDrv7A";
86    pub const TEST_FOLDER_KEY: &str = "xsXXTpoYEFDRQdeHPDrv7A";
87    pub const TEST_FOLDER_ID: &str = "MWsm3aBL";
88
89    pub const TEST_FOLDER_NESTED: &str =
90        "https://mega.nz/folder/MWsm3aBL#xsXXTpoYEFDRQdeHPDrv7A/folder/IGlBlD6K";
91
92    pub const TEST_FILE_KEY_KEY_DECODED: u128 = u128::from_be_bytes([
93        161, 141, 109, 44, 84, 62, 135, 130, 36, 158, 235, 166, 55, 235, 206, 43,
94    ]);
95    pub const TEST_FILE_KEY_IV_DECODED: u128 =
96        u128::from_be_bytes([182, 162, 49, 236, 174, 124, 29, 100, 0, 0, 0, 0, 0, 0, 0, 0]);
97    pub const TEST_FILE_META_MAC_DECODED: u64 =
98        u64::from_be_bytes([177, 234, 162, 176, 224, 49, 126, 47]);
99    pub const TEST_FOLDER_KEY_DECODED: u128 = u128::from_be_bytes([
100        198, 197, 215, 78, 154, 24, 16, 80, 209, 65, 215, 135, 60, 58, 239, 236,
101    ]);
102
103    pub const TEST_FILE_BYTES: &[u8] = include_bytes!("../test_data/Doxygen_docs.zip");
104
105    #[test]
106    fn parse_file_key() {
107        let file_key: FileKey = TEST_FILE_KEY.parse().expect("failed to parse file key");
108        assert!(file_key.key == TEST_FILE_KEY_KEY_DECODED);
109        assert!(file_key.iv == TEST_FILE_KEY_IV_DECODED);
110        assert!(file_key.meta_mac == TEST_FILE_META_MAC_DECODED);
111    }
112
113    #[test]
114    fn parse_folder_key() {
115        let folder_key: FolderKey = TEST_FOLDER_KEY.parse().expect("failed to parse folder key");
116        assert!(folder_key.0 == TEST_FOLDER_KEY_DECODED);
117    }
118
119    #[tokio::test]
120    async fn download_file() {
121        let file_key = FileKey {
122            key: TEST_FILE_KEY_KEY_DECODED,
123            iv: TEST_FILE_KEY_IV_DECODED,
124            meta_mac: TEST_FILE_META_MAC_DECODED,
125        };
126
127        let client = Client::new();
128        let commands = vec![Command::GetAttributes {
129            public_node_id: Some(TEST_FILE_ID.into()),
130            node_id: None,
131            include_download_url: Some(1),
132        }];
133        let mut response = client
134            .execute_commands(&commands, None)
135            .await
136            .expect("failed to execute commands");
137        assert!(response.len() == 1);
138        let response = response.swap_remove(0);
139        let response = response.into_result().expect("response was an error");
140        let response = match response {
141            ResponseData::GetAttributes(response) => response,
142            _ => panic!("unexpected response"),
143        };
144        let download_url = response
145            .download_url
146            .as_ref()
147            .expect("missing download url");
148
149        let mut response = client
150            .client
151            .get(download_url.as_str())
152            .send()
153            .await
154            .expect("failed to send")
155            .error_for_status()
156            .expect("invalid status");
157        let mut cipher = Aes128Ctr128BE::new(
158            &file_key.key.to_be_bytes().into(),
159            &file_key.iv.to_be_bytes().into(),
160        );
161
162        let mut output = Vec::new();
163        let mut validator = FileValidator::new(file_key.clone());
164        while let Some(chunk) = response.chunk().await.expect("failed to get chunk") {
165            let old_len = output.len();
166            output.extend(&chunk);
167            cipher.apply_keystream(&mut output[old_len..]);
168        }
169        assert!(output == TEST_FILE_BYTES);
170
171        validator.feed(&output);
172        validator.finish().expect("validation failed");
173    }
174}