1use crate::FileKey;
2use crate::FileKeyParseError;
3use crate::FolderKey;
4use crate::FolderKeyParseError;
5use url::Url;
6
7#[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#[derive(Debug)]
22pub enum ParsedMegaUrl {
23 File(ParsedMegaFileUrl),
24 Folder(ParsedMegaFolderUrl),
25}
26
27impl ParsedMegaUrl {
28 pub fn as_file_url(&self) -> Option<&ParsedMegaFileUrl> {
30 match self {
31 Self::File(file) => Some(file),
32 _ => None,
33 }
34 }
35
36 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#[derive(Debug)]
140pub struct ParsedMegaFileUrl {
141 pub file_id: String,
143
144 pub file_key: FileKey,
146}
147
148#[derive(Debug)]
150pub struct ParsedMegaFolderUrl {
151 pub folder_id: String,
153
154 pub folder_key: FolderKey,
156
157 pub child_data: Option<ParsedMegaFolderUrlChildData>,
159}
160
161#[derive(Debug)]
162pub struct ParsedMegaFolderUrlChildData {
163 pub is_file: bool,
165
166 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}