1use hyper::header::WWW_AUTHENTICATE;
2use hyper::StatusCode;
3use reqwest::blocking::Client;
4use std::fmt::{Debug, Formatter};
5use std::io::{Read, Seek, Write};
6use vfs::{FileSystem, SeekAndRead, VfsError, VfsMetadata, VfsResult};
7
8use crate::error::AuthError;
9use crate::error::HttpsFSError;
10use crate::error::HttpsFSResult;
11
12use crate::protocol::*;
13
14type CredentialProvider = Option<fn(realm: &str) -> (String, String)>;
15
16pub struct HttpsFS {
18 addr: String,
19 client: std::sync::Arc<reqwest::blocking::Client>,
20 credentials: CredentialProvider,
23}
24
25pub struct HttpsFSBuilder {
27 port: u16,
28 domain: String,
29 root_certs: Vec<reqwest::Certificate>,
30 credentials: CredentialProvider,
31}
32
33struct WritableFile {
34 client: std::sync::Arc<reqwest::blocking::Client>,
35 addr: String,
36 file_name: String,
37 position: u64,
38}
39
40struct ReadableFile {
41 client: std::sync::Arc<reqwest::blocking::Client>,
42 addr: String,
43 file_name: String,
44 position: u64,
45}
46
47impl Debug for HttpsFS {
48 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
49 f.write_str("Over Https Exposed File System.")
50 }
51}
52
53impl HttpsFS {
54 pub fn builder(domain: &str) -> HttpsFSBuilder {
56 HttpsFSBuilder::new(domain)
57 }
58
59 fn load_certificate(filename: &str) -> HttpsFSResult<reqwest::Certificate> {
60 let mut buf = Vec::new();
61 std::fs::File::open(filename)?.read_to_end(&mut buf)?;
62 let cert = reqwest::Certificate::from_pem(&buf)?;
63 Ok(cert)
64 }
65
66 fn exec_command(&self, cmd: &Command) -> HttpsFSResult<CommandResponse> {
67 let req = serde_json::to_string(&cmd)?;
68 let mut result = self.client.post(&self.addr).body(req).send()?;
69 if result.status() == StatusCode::UNAUTHORIZED {
70 let req = serde_json::to_string(&cmd)?;
71 result = self
72 .authorize(&result, self.client.post(&self.addr).body(req))?
73 .send()?;
74 if result.status() != StatusCode::OK {
75 return Err(HttpsFSError::Auth(AuthError::Failed));
76 }
77 }
78 let result = result.text()?;
79 let result: CommandResponse = serde_json::from_str(&result)?;
80 Ok(result)
81 }
82
83 fn authorize(
84 &self,
85 prev_response: &reqwest::blocking::Response,
86 new_request: reqwest::blocking::RequestBuilder,
87 ) -> HttpsFSResult<reqwest::blocking::RequestBuilder> {
88 if self.credentials.is_none() {
89 return Err(HttpsFSError::Auth(AuthError::NoCredentialSource));
90 }
91 let prev_headers = prev_response.headers();
92 let auth_method = prev_headers
93 .get(WWW_AUTHENTICATE)
94 .ok_or(HttpsFSError::Auth(AuthError::NoMethodSpecified))?;
95 let auth_method = String::from(
96 auth_method
97 .to_str()
98 .map_err(|_| HttpsFSError::InvalidHeader(WWW_AUTHENTICATE.to_string()))?,
99 );
100 let start_with = "Basic realm=\"PME\"";
104 if !auth_method.starts_with(start_with) {
105 return Err(HttpsFSError::Auth(AuthError::MethodNotSupported));
106 }
107 let get_cred = self.credentials.unwrap();
108 let (username, password) = get_cred(&"PME");
109 let new_request = new_request.basic_auth(username, Some(password));
110 Ok(new_request)
111 }
112}
113
114impl HttpsFSBuilder {
115 pub fn new(domain: &str) -> Self {
119 HttpsFSBuilder {
120 port: 443,
121 domain: String::from(domain),
122 root_certs: Vec::new(),
123 credentials: None,
124 }
125 }
126
127 pub fn set_port(mut self, port: u16) -> Self {
131 self.port = port;
132 self
133 }
134
135 pub fn set_domain(mut self, domain: &str) -> Self {
137 self.domain = String::from(domain);
138 self
139 }
140
141 pub fn add_root_certificate(mut self, cert: &str) -> Self {
147 let cert = HttpsFS::load_certificate(cert).unwrap();
148 self.root_certs.push(cert);
149 self
150 }
151
152 pub fn set_credential_provider(
156 mut self,
157 c_provider: fn(realm: &str) -> (String, String),
158 ) -> Self {
159 self.credentials = Some(c_provider);
160 self
161 }
162
163 pub fn build(self) -> HttpsFSResult<HttpsFS> {
169 if self.credentials.is_none() {
170 return Err(HttpsFSError::Other {
171 message: "HttpsFSBuilder: No credential provider set.".to_string(),
172 });
173 }
174 let mut client = Client::builder().https_only(true).cookie_store(true);
175 for cert in self.root_certs {
176 client = client.add_root_certificate(cert);
177 }
178
179 let client = client.build()?;
180 Ok(HttpsFS {
181 client: std::sync::Arc::new(client),
182 addr: format!("https://{}:{}/", self.domain, self.port),
183 credentials: self.credentials,
184 })
185 }
186}
187
188impl Write for WritableFile {
189 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
190 let req = Command::Write(CommandWrite {
191 path: self.file_name.clone(),
192 pos: self.position,
193 len: buf.len() as u64,
194 data: base64::encode(buf),
195 });
196 let req = serde_json::to_string(&req)?;
197 let result = self.client.post(&self.addr).body(req).send();
198 if let Err(e) = result {
199 return Err(std::io::Error::new(
200 std::io::ErrorKind::Other,
201 format!("{:?}", e),
202 ));
203 }
204 let result = result.unwrap();
205 let result = result.text();
206 if let Err(e) = result {
207 return Err(std::io::Error::new(
208 std::io::ErrorKind::Other,
209 format!("{:?}", e),
210 ));
211 }
212 let result = result.unwrap();
213 let result: CommandResponse = serde_json::from_str(&result)?;
214 match result {
215 CommandResponse::Write(result) => match result {
216 Ok(size) => {
217 self.position += size as u64;
218 Ok(size)
219 }
220 Err(e) => Err(std::io::Error::new(
221 std::io::ErrorKind::Other,
222 format!("{:?}", e),
223 )),
224 },
225 _ => Err(std::io::Error::new(
226 std::io::ErrorKind::Other,
227 String::from("Result doesn't match the request!"),
228 )),
229 }
230 }
231
232 fn flush(&mut self) -> std::io::Result<()> {
233 todo!("flush()");
234 }
235}
236
237impl Read for ReadableFile {
238 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
239 let req = Command::Read(CommandRead {
240 path: self.file_name.clone(),
241 pos: self.position,
242 len: buf.len() as u64,
243 });
244 let req = serde_json::to_string(&req)?;
245 let result = self.client.post(&self.addr).body(req).send();
246 if let Err(e) = result {
247 return Err(std::io::Error::new(
248 std::io::ErrorKind::Other,
249 format!("{:?}", e),
250 ));
251 }
252 let result = result.unwrap();
253 let result = result.text();
254 if let Err(e) = result {
255 return Err(std::io::Error::new(
256 std::io::ErrorKind::Other,
257 format!("{:?}", e),
258 ));
259 }
260 let result = result.unwrap();
261 let result: CommandResponse = serde_json::from_str(&result)?;
262 match result {
263 CommandResponse::Read(result) => match result {
264 Ok((size, data)) => {
265 self.position += size as u64;
266 let decoded_data = base64::decode(data);
267 let mut result = Err(std::io::Error::new(
268 std::io::ErrorKind::Other,
269 String::from("Faild to decode data"),
270 ));
271 if let Ok(data) = decoded_data {
272 buf[..size].copy_from_slice(&data.as_slice()[..size]);
273 result = Ok(size);
274 }
275 result
276 }
277 Err(e) => Err(std::io::Error::new(
278 std::io::ErrorKind::Other,
279 format!("{:?}", e),
280 )),
281 },
282 _ => Err(std::io::Error::new(
283 std::io::ErrorKind::Other,
284 String::from("Result doesn't match the request!"),
285 )),
286 }
287 }
288}
289
290impl Seek for ReadableFile {
291 fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
292 match pos {
293 std::io::SeekFrom::Start(offset) => self.position = offset,
294 std::io::SeekFrom::Current(offset) => {
295 self.position = (self.position as i64 + offset) as u64
296 }
297 std::io::SeekFrom::End(offset) => {
298 let fs = HttpsFS {
299 addr: self.addr.clone(),
300 client: self.client.clone(),
301 credentials: None,
302 };
303 let meta = fs.metadata(&self.file_name);
304 if let Err(e) = meta {
305 return Err(std::io::Error::new(
306 std::io::ErrorKind::Other,
307 format!("{:?}", e),
308 ));
309 }
310 let meta = meta.unwrap();
311 self.position = (meta.len as i64 + offset) as u64
312 }
313 }
314 Ok(self.position)
315 }
316}
317
318impl FileSystem for HttpsFS {
319 fn read_dir(&self, path: &str) -> VfsResult<Box<dyn Iterator<Item = String>>> {
320 let req = Command::ReadDir(CommandReadDir {
321 path: String::from(path),
322 });
323 let result = self.exec_command(&req)?;
324 let result = match result {
325 CommandResponse::ReadDir(value) => value,
326 _ => {
327 return Err(VfsError::Other {
328 message: String::from("Result doesn't match the request!"),
329 });
330 }
331 };
332 match result.result {
333 Err(e) => Err(VfsError::Other { message: e }),
334 Ok(value) => Ok(Box::new(value.into_iter())),
335 }
336 }
337
338 fn create_dir(&self, path: &str) -> VfsResult<()> {
339 let req = Command::CreateDir(CommandCreateDir {
340 path: String::from(path),
341 });
342 let result = self.exec_command(&req)?;
343 let result = match result {
344 CommandResponse::CreateDir(value) => value,
345 _ => {
346 return Err(VfsError::Other {
347 message: String::from("Result doesn't match the request!"),
348 });
349 }
350 };
351
352 match result {
353 CommandResponseCreateDir::Failed => Err(VfsError::Other {
354 message: String::from("Result doesn't match the request!"),
355 }),
356 CommandResponseCreateDir::Success => Ok(()),
357 }
358 }
359
360 fn open_file(&self, path: &str) -> VfsResult<Box<dyn SeekAndRead>> {
361 if !self.exists(path)? {
362 return Err(VfsError::FileNotFound {
363 path: path.to_string(),
364 });
365 }
366
367 Ok(Box::new(ReadableFile {
368 client: self.client.clone(),
369 addr: self.addr.clone(),
370 file_name: String::from(path),
371 position: 0,
372 }))
373 }
374
375 fn create_file(&self, path: &str) -> VfsResult<Box<dyn Write>> {
376 let req = Command::CreateFile(CommandCreateFile {
377 path: String::from(path),
378 });
379 let result = self.exec_command(&req)?;
380 let result = match result {
381 CommandResponse::CreateFile(value) => value,
382 _ => {
383 return Err(VfsError::Other {
384 message: String::from("Result doesn't match the request!"),
385 });
386 }
387 };
388
389 match result {
390 CommandResponseCreateFile::Failed => Err(VfsError::Other {
391 message: String::from("Faild to create file!"),
392 }),
393 CommandResponseCreateFile::Success => Ok(Box::new(WritableFile {
394 client: self.client.clone(),
395 addr: self.addr.clone(),
396 file_name: String::from(path),
397 position: 0,
398 })),
399 }
400 }
401
402 fn append_file(&self, path: &str) -> VfsResult<Box<dyn Write>> {
403 let meta = self.metadata(path)?;
404 Ok(Box::new(WritableFile {
405 client: self.client.clone(),
406 addr: self.addr.clone(),
407 file_name: String::from(path),
408 position: meta.len,
409 }))
410 }
411
412 fn metadata(&self, path: &str) -> VfsResult<VfsMetadata> {
413 let req = Command::Metadata(CommandMetadata {
414 path: String::from(path),
415 });
416 let result = self.exec_command(&req)?;
417 match result {
418 CommandResponse::Metadata(value) => meta_res_convert_cmd_vfs(value),
419 _ => Err(VfsError::Other {
420 message: String::from("Result doesn't match the request!"),
421 }),
422 }
423 }
424
425 fn exists(&self, path: &str) -> VfsResult<bool> {
426 let req = Command::Exists(CommandExists {
431 path: String::from(path),
432 });
433 let result = self.exec_command(&req)?;
434 let result = match result {
435 CommandResponse::Exists(value) => value,
436 _ => {
437 return Err(VfsError::Other {
438 message: String::from("Result doesn't match the request!"),
439 });
440 }
441 };
442 match result {
443 Err(e) => Err(VfsError::Other {
444 message: format!("{:?}", e),
445 }),
446 Ok(val) => Ok(val),
447 }
448 }
449
450 fn remove_file(&self, path: &str) -> VfsResult<()> {
451 let req = Command::RemoveFile(CommandRemoveFile {
452 path: String::from(path),
453 });
454 let result = self.exec_command(&req)?;
455 let result = match result {
456 CommandResponse::RemoveFile(value) => value,
457 _ => {
458 return Err(VfsError::Other {
459 message: String::from("Result doesn't match the request!"),
460 });
461 }
462 };
463
464 match result {
465 Err(e) => Err(VfsError::Other {
466 message: format!("{:?}", e),
467 }),
468 Ok(_) => Ok(()),
469 }
470 }
471
472 fn remove_dir(&self, path: &str) -> VfsResult<()> {
473 let req = Command::RemoveDir(CommandRemoveDir {
474 path: String::from(path),
475 });
476 let result = self.exec_command(&req)?;
477 let result = match result {
478 CommandResponse::RemoveDir(value) => value,
479 _ => {
480 return Err(VfsError::Other {
481 message: String::from("Result doesn't match the request!"),
482 });
483 }
484 };
485
486 match result {
487 Err(e) => Err(VfsError::Other {
488 message: format!("{:?}", e),
489 }),
490 Ok(_) => Ok(()),
491 }
492 }
493}
494
495#[cfg(test)]
496mod tests {
497 use crate::{HttpsFS, HttpsFSServer};
498 use lazy_static::lazy_static;
499 use std::sync::{Arc, Mutex};
500 use vfs::{test_vfs, MemoryFS};
501
502 lazy_static! {
509 static ref PORT: Arc<Mutex<u16>> = Arc::new(Mutex::new(8344));
510 }
511
512 test_vfs!({
513 let server_port;
514 match PORT.lock() {
515 Ok(mut x) => {
516 println!("Number: {}", *x);
517 server_port = *x;
518 *x += 1;
519 }
520 Err(e) => panic!("Error: {:?}", e),
521 }
522 std::thread::spawn(move || {
523 let fs = MemoryFS::new();
524 let server = HttpsFSServer::builder(fs)
525 .set_port(server_port)
526 .load_certificates("examples/cert/cert.crt")
527 .load_private_key("examples/cert/private-key.key")
528 .set_credential_validator(|username: &str, password: &str| {
529 username == "user" && password == "pass"
530 });
531 let result = server.run();
532 if let Err(e) = result {
533 println!("WARNING: {:?}", e);
534 }
535 });
536
537 let duration = std::time::Duration::from_millis(10);
539 std::thread::sleep(duration);
540
541 HttpsFS::builder("localhost")
542 .set_port(server_port)
543 .add_root_certificate("examples/cert/cert.crt")
547 .set_credential_provider(|_| (String::from("user"), String::from("pass")))
548 .build()
549 .unwrap()
550 });
551}