1use async_trait::async_trait;
43use cdfs::{DirectoryEntry, ExtraAttributes, ISO9660, ISODirectory, ISOFileReader};
44use libunftp::{
45 auth::UserDetail,
46 storage::{Error, ErrorKind},
47 storage::{Fileinfo, Metadata, Result, StorageBackend},
48};
49use std::{
50 fmt::Debug,
51 fs::File,
52 io::{Cursor, Read, Seek, SeekFrom},
53 path::{Path, PathBuf},
54 time::SystemTime,
55};
56use tokio::io::AsyncRead;
57
58#[derive(Debug, Clone)]
60pub struct Storage {
61 iso_path: PathBuf,
62}
63
64impl Storage {
65 pub fn new<P: AsRef<Path>>(iso_path: P) -> Self {
68 Self {
69 iso_path: iso_path.as_ref().to_path_buf(),
70 }
71 }
72
73 fn open_iso(&self) -> std::io::Result<ISO9660<std::fs::File>> {
74 let file = std::fs::File::open(&self.iso_path)?;
75 Ok(ISO9660::new(file).unwrap())
76 }
77
78 fn find<P: AsRef<Path> + Send + Debug>(&self, path: P) -> Result<DirectoryEntry<File>> {
79 let iso: ISO9660<File> = self.open_iso()?;
80 let mut current_dir: ISODirectory<File> = iso.root().clone();
81
82 let mut components = path.as_ref().components().peekable();
83
84 while let Some(comp) = components.next() {
85 use std::path::Component;
86
87 let name = match comp {
88 Component::RootDir => continue,
89 Component::Normal(name) => name.to_str().unwrap().to_uppercase(),
90 _ => {
91 return Err(Error::new(
92 ErrorKind::PermanentFileNotAvailable,
93 "Unsupported path component",
94 ));
95 }
96 };
97
98 let next_entry: DirectoryEntry<File> = current_dir
100 .contents()
101 .filter_map(|e| e.ok())
102 .find(|e| e.identifier().eq_ignore_ascii_case(&name))
103 .ok_or_else(|| {
104 Error::new(
105 ErrorKind::TransientFileNotAvailable,
106 format!("Path component '{}' not found", name),
107 )
108 })?;
109
110 if components.peek().is_none() {
111 return Ok(next_entry);
113 }
114
115 match next_entry {
117 DirectoryEntry::Directory(dir) => {
118 current_dir = dir; }
120 _ => {
121 return Err(Error::new(
122 ErrorKind::PermanentFileNotAvailable,
123 "Intermediate path component is not a directory",
124 ));
125 }
126 }
127 }
128
129 Ok(DirectoryEntry::Directory(current_dir))
131 }
132}
133
134#[async_trait]
135impl<User: UserDetail> StorageBackend<User> for Storage {
136 type Metadata = IsoMeta;
137
138 async fn metadata<P: AsRef<Path> + Send + Debug>(
139 &self,
140 _user: &User,
141 path: P,
142 ) -> Result<Self::Metadata> {
143 let entry = self.find(path)?;
144 let size = match &entry {
145 DirectoryEntry::Directory(d) => d.header().length as u64,
146 DirectoryEntry::File(f) => f.size() as u64,
147 DirectoryEntry::Symlink(l) => l.header().length as u64,
148 };
149 Ok(IsoMeta {
150 len: size,
151 dir: matches!(entry, DirectoryEntry::Directory(_)),
152 sym: matches!(entry, DirectoryEntry::Symlink(_)),
153 group: entry.group().unwrap_or(0),
154 owner: entry.owner().unwrap_or(0),
155 modified: entry.modify_time().into(),
156 })
157 }
158
159 async fn list<P: AsRef<Path> + Send + Debug>(
160 &self,
161 _user: &User,
162 path: P,
163 ) -> Result<Vec<Fileinfo<PathBuf, Self::Metadata>>>
164 where
165 <Self as StorageBackend<User>>::Metadata: Metadata,
166 {
167 let mut entries = Vec::new();
168 let e = self.find(path)?;
169 let d = match e {
170 DirectoryEntry::Directory(d) => d,
171 DirectoryEntry::File(_) => return Err(Error::from(ErrorKind::FileNameNotAllowedError)),
172 DirectoryEntry::Symlink(_) => {
173 return Err(Error::from(ErrorKind::FileNameNotAllowedError));
174 }
175 };
176 for entry in d.contents() {
177 let e = entry.unwrap();
178 let size = match &e {
179 DirectoryEntry::Directory(d) => d.header().length as u64,
180 DirectoryEntry::File(f) => f.size() as u64,
181 DirectoryEntry::Symlink(l) => l.header().length as u64,
182 };
183 entries.push(Fileinfo {
184 path: e.identifier().into(),
185 metadata: IsoMeta {
186 len: size,
187 dir: matches!(e, DirectoryEntry::Directory(_)),
188 sym: matches!(e, DirectoryEntry::Symlink(_)),
189 group: e.group().unwrap_or(0),
190 owner: e.owner().unwrap_or(0),
191 modified: e.modify_time().into(),
192 },
193 });
194 }
195 Ok(entries)
196 }
197
198 async fn get<P: AsRef<Path> + Send + Debug>(
199 &self,
200 _user: &User,
201 path: P,
202 start_pos: u64,
203 ) -> Result<Box<dyn AsyncRead + Send + Sync + Unpin>> {
204 let entry: DirectoryEntry<File> = self.find(path)?;
205 match entry {
206 DirectoryEntry::File(file_entry) => {
207 let mut reader: ISOFileReader<File> = file_entry.read();
208 if start_pos > 0 {
210 reader.seek(SeekFrom::Start(start_pos)).map_err(|e| {
211 Error::new(
212 ErrorKind::PermanentFileNotAvailable,
213 format!("seek error: {e}"),
214 )
215 })?;
216 }
217
218 let mut buf = Vec::new();
220 reader.read_to_end(&mut buf).map_err(|e| {
221 Error::new(
222 ErrorKind::PermanentFileNotAvailable,
223 format!("read error: {e}"),
224 )
225 })?;
226
227 let cursor = Cursor::new(buf);
229 Ok(Box::new(cursor))
230 }
231
232 DirectoryEntry::Directory(_) => Err(ErrorKind::PermanentFileNotAvailable.into()),
233 DirectoryEntry::Symlink(_) => Err(ErrorKind::PermanentFileNotAvailable.into()),
234 }
235 }
236
237 async fn put<P: AsRef<Path> + Send + Debug, R: AsyncRead + Send + Sync + Unpin + 'static>(
238 &self,
239 _user: &User,
240 _input: R,
241 _path: P,
242 _start_pos: u64,
243 ) -> Result<u64> {
244 Err(Error::from(ErrorKind::PermissionDenied))
245 }
246
247 async fn del<P: AsRef<Path> + Send + Debug>(&self, _user: &User, _path: P) -> Result<()> {
248 Err(Error::from(ErrorKind::PermissionDenied))
249 }
250
251 async fn mkd<P: AsRef<Path> + Send + Debug>(&self, _user: &User, _path: P) -> Result<()> {
252 Err(Error::from(ErrorKind::PermissionDenied))
253 }
254
255 async fn rename<P: AsRef<Path> + Send + Debug>(
256 &self,
257 _user: &User,
258 _from: P,
259 _to: P,
260 ) -> Result<()> {
261 Err(Error::from(ErrorKind::PermissionDenied))
262 }
263
264 async fn rmd<P: AsRef<Path> + Send + Debug>(&self, _user: &User, _path: P) -> Result<()> {
265 Err(Error::from(ErrorKind::PermissionDenied))
266 }
267
268 async fn cwd<P: AsRef<Path> + Send + Debug>(&self, _user: &User, path: P) -> Result<()> {
269 self.find(path).map(|_d| ())
270 }
271}
272
273#[derive(Debug)]
275pub struct IsoMeta {
276 pub len: u64,
278 pub dir: bool,
280 pub sym: bool,
282 pub group: u32,
284 pub owner: u32,
286 pub modified: SystemTime,
288}
289
290impl Metadata for IsoMeta {
291 fn len(&self) -> u64 {
292 self.len
293 }
294
295 fn is_dir(&self) -> bool {
296 self.dir
297 }
298
299 fn is_file(&self) -> bool {
300 !self.dir
301 }
302
303 fn is_symlink(&self) -> bool {
304 false
305 }
306
307 fn modified(&self) -> Result<SystemTime> {
308 Ok(self.modified)
309 }
310
311 fn gid(&self) -> u32 {
312 self.group
313 }
314
315 fn uid(&self) -> u32 {
316 self.owner
317 }
318}