mega/
parsed_mega_url.rs

1use crate::FileKey;
2use crate::FileKeyParseError;
3use crate::FolderKey;
4use crate::FolderKeyParseError;
5use url::Url;
6
7/// An error that may occur while parsing a mega url.
8#[derive(Debug, thiserror::Error)]
9pub enum ParseMegaUrlError {
10    #[error("invalid file key")]
11    InvalidFileKey(#[source] FileKeyParseError),
12
13    #[error("invalid folder key")]
14    InvalidFolderKey(#[source] FolderKeyParseError),
15
16    #[error("{0}")]
17    Generic(&'static str),
18}
19
20/// A parsed mega url.
21#[derive(Debug)]
22pub enum ParsedMegaUrl {
23    File(ParsedMegaFileUrl),
24    Folder(ParsedMegaFolderUrl),
25}
26
27impl ParsedMegaUrl {
28    /// Get as ref to the file url struct if it is a file url.
29    pub fn as_file_url(&self) -> Option<&ParsedMegaFileUrl> {
30        match self {
31            Self::File(file) => Some(file),
32            _ => None,
33        }
34    }
35
36    /// Get as ref to the folder url struct if it is a folder url.
37    pub fn as_folder_url(&self) -> Option<&ParsedMegaFolderUrl> {
38        match self {
39            Self::Folder(file) => Some(file),
40            _ => None,
41        }
42    }
43}
44
45impl TryFrom<&Url> for ParsedMegaUrl {
46    type Error = ParseMegaUrlError;
47
48    fn try_from(url: &Url) -> Result<Self, Self::Error> {
49        if url.host_str() != Some("mega.nz") {
50            return Err(ParseMegaUrlError::Generic("invalid host"));
51        }
52
53        let mut path_iter = url
54            .path_segments()
55            .ok_or(ParseMegaUrlError::Generic("missing path"))?;
56
57        match path_iter.next() {
58            Some("file") => {
59                let file_id = path_iter
60                    .next()
61                    .ok_or(ParseMegaUrlError::Generic("missing file id path segment"))?;
62
63                if path_iter.next().is_some() {
64                    return Err(ParseMegaUrlError::Generic(
65                        "expected the path to end, but it continued",
66                    ));
67                }
68
69                let file_key_raw = url
70                    .fragment()
71                    .ok_or(ParseMegaUrlError::Generic("missing file key"))?;
72                let file_key: FileKey = file_key_raw
73                    .parse()
74                    .map_err(ParseMegaUrlError::InvalidFileKey)?;
75
76                Ok(Self::File(ParsedMegaFileUrl {
77                    file_id: file_id.to_string(),
78                    file_key,
79                }))
80            }
81            Some("folder") => {
82                let folder_id = path_iter
83                    .next()
84                    .ok_or(ParseMegaUrlError::Generic("missing folder id path segment"))?;
85
86                if path_iter.next().is_some() {
87                    return Err(ParseMegaUrlError::Generic(
88                        "expected the path to end, but it continued",
89                    ));
90                }
91
92                let folder_key_raw = url
93                    .fragment()
94                    .ok_or(ParseMegaUrlError::Generic("missing folder key"))?;
95                let (folder_key_raw, rest) = folder_key_raw
96                    .split_once('/')
97                    .unwrap_or((folder_key_raw, ""));
98
99                let child_data = if !rest.is_empty() {
100                    let (kind, node_id) = rest
101                        .split_once('/')
102                        .ok_or(ParseMegaUrlError::Generic("unknown fragment format"))?;
103
104                    let is_file = match kind {
105                        "file" => true,
106                        "folder" => false,
107                        _ => {
108                            return Err(ParseMegaUrlError::Generic(
109                                "unknown fragment path segment",
110                            ));
111                        }
112                    };
113
114                    Some(ParsedMegaFolderUrlChildData {
115                        is_file,
116                        node_id: node_id.to_string(),
117                    })
118                } else {
119                    None
120                };
121
122                let folder_key: FolderKey = folder_key_raw
123                    .parse()
124                    .map_err(ParseMegaUrlError::InvalidFolderKey)?;
125
126                Ok(Self::Folder(ParsedMegaFolderUrl {
127                    folder_id: folder_id.to_string(),
128                    folder_key,
129                    child_data,
130                }))
131            }
132            Some(_) => Err(ParseMegaUrlError::Generic("unknown path segment")),
133            None => Err(ParseMegaUrlError::Generic("missing path segment")),
134        }
135    }
136}
137
138/// A parsed file url
139#[derive(Debug)]
140pub struct ParsedMegaFileUrl {
141    /// The public file id
142    pub file_id: String,
143
144    /// The file key
145    pub file_key: FileKey,
146}
147
148/// A parsed folder url
149#[derive(Debug)]
150pub struct ParsedMegaFolderUrl {
151    /// The folder id
152    pub folder_id: String,
153
154    ///The folder key
155    pub folder_key: FolderKey,
156
157    /// Child data
158    pub child_data: Option<ParsedMegaFolderUrlChildData>,
159}
160
161#[derive(Debug)]
162pub struct ParsedMegaFolderUrlChildData {
163    /// If true, this is a file. Otherwise, it is a folder.
164    pub is_file: bool,
165
166    /// The node id.
167    pub node_id: String,
168}
169
170#[cfg(test)]
171mod test {
172    use super::*;
173    use crate::test::*;
174
175    #[test]
176    fn test_parse_file_url() {
177        let url = Url::parse(TEST_FILE).unwrap();
178
179        let parsed = ParsedMegaUrl::try_from(&url).expect("failed to parse url");
180        let parsed = parsed.as_file_url().expect("not a file url");
181        assert!(parsed.file_id == TEST_FILE_ID);
182        assert!(parsed.file_key.key == TEST_FILE_KEY_KEY_DECODED);
183        assert!(parsed.file_key.iv == TEST_FILE_KEY_IV_DECODED);
184        assert!(parsed.file_key.meta_mac == TEST_FILE_META_MAC_DECODED);
185    }
186
187    #[test]
188    fn test_parse_folder_url() {
189        let url = Url::parse(TEST_FOLDER).unwrap();
190
191        let parsed = ParsedMegaUrl::try_from(&url).expect("failed to parse url");
192        let parsed = parsed.as_folder_url().expect("not a folder url");
193        assert!(parsed.folder_id == "MWsm3aBL");
194        assert!(parsed.folder_key.0 == TEST_FOLDER_KEY_DECODED);
195        assert!(parsed.child_data.is_none());
196    }
197
198    #[test]
199    fn test_parse_folder_nested_url() {
200        let url = Url::parse(TEST_FOLDER_NESTED).unwrap();
201
202        let parsed = ParsedMegaUrl::try_from(&url).expect("failed to parse url");
203        let parsed = parsed.as_folder_url().expect("not a folder url");
204        assert!(parsed.folder_id == "MWsm3aBL");
205        assert!(parsed.folder_key.0 == TEST_FOLDER_KEY_DECODED);
206        let child_data = parsed.child_data.as_ref().expect("missing child data");
207        assert!(!child_data.is_file);
208        assert!(child_data.node_id == "IGlBlD6K");
209    }
210}