Skip to main content

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