1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// Copyright 2019-2022 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

//! Types and functions related to file operations.

#[cfg(feature = "fs-extract-api")]
mod extract;
mod file_move;

use std::{
  fs,
  path::{Display, Path},
};

#[cfg(feature = "fs-extract-api")]
pub use extract::*;
pub use file_move::*;

use serde::{de::Error as DeError, Deserialize, Deserializer};

#[derive(Clone, Debug)]
pub(crate) struct SafePathBuf(std::path::PathBuf);

impl SafePathBuf {
  pub fn new(path: std::path::PathBuf) -> Result<Self, &'static str> {
    if path
      .components()
      .any(|x| matches!(x, std::path::Component::ParentDir))
    {
      Err("cannot traverse directory, rewrite the path without the use of `../`")
    } else {
      Ok(Self(path))
    }
  }

  #[allow(dead_code)]
  pub unsafe fn new_unchecked(path: std::path::PathBuf) -> Self {
    Self(path)
  }

  #[allow(dead_code)]
  pub fn display(&self) -> Display<'_> {
    self.0.display()
  }
}

impl AsRef<Path> for SafePathBuf {
  fn as_ref(&self) -> &Path {
    self.0.as_ref()
  }
}

impl<'de> Deserialize<'de> for SafePathBuf {
  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
  where
    D: Deserializer<'de>,
  {
    let path = std::path::PathBuf::deserialize(deserializer)?;
    SafePathBuf::new(path).map_err(DeError::custom)
  }
}

/// Reads the entire contents of a file into a string.
pub fn read_string<P: AsRef<Path>>(file: P) -> crate::api::Result<String> {
  fs::read_to_string(file).map_err(Into::into)
}

/// Reads the entire contents of a file into a bytes vector.
pub fn read_binary<P: AsRef<Path>>(file: P) -> crate::api::Result<Vec<u8>> {
  fs::read(file).map_err(Into::into)
}

#[cfg(test)]
mod test {
  use super::*;
  #[cfg(not(windows))]
  use crate::api::Error;
  use quickcheck::{Arbitrary, Gen};

  use std::path::PathBuf;

  impl Arbitrary for super::SafePathBuf {
    fn arbitrary(g: &mut Gen) -> Self {
      Self(PathBuf::arbitrary(g))
    }

    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
      Box::new(self.0.shrink().map(SafePathBuf))
    }
  }

  #[test]
  fn check_read_string() {
    let file = String::from("test/api/test.txt");

    let res = read_string(file);

    assert!(res.is_ok());

    if let Ok(s) = res {
      assert_eq!(s, "This is a test doc!".to_string());
    }
  }

  #[test]
  fn check_read_string_fail() {
    let file = String::from("test/api/");

    let res = read_string(file);

    assert!(res.is_err());

    #[cfg(not(windows))]
    if let Error::Io(e) = res.unwrap_err() {
      #[cfg(not(windows))]
      assert_eq!(e.to_string(), "Is a directory (os error 21)".to_string());
    }
  }

  #[test]
  fn check_read_binary() {
    let file = String::from("test/api/test_binary");

    let expected_vec = vec![
      71, 73, 70, 56, 57, 97, 1, 0, 1, 0, 128, 0, 0, 255, 255, 255, 0, 0, 0, 33, 249, 4, 1, 0, 0,
      0, 0, 44, 0, 0, 0, 0, 1, 0, 1, 0, 0, 2, 2, 68, 1, 0, 59,
    ];

    let res = read_binary(file);

    assert!(res.is_ok());

    if let Ok(vec) = res {
      assert_eq!(vec, expected_vec);
    }
  }

  #[test]
  fn check_read_binary_fail() {
    let file = String::from("test/api/");

    let res = read_binary(file);

    assert!(res.is_err());

    #[cfg(not(windows))]
    if let Error::Io(e) = res.unwrap_err() {
      #[cfg(not(windows))]
      assert_eq!(e.to_string(), "Is a directory (os error 21)".to_string());
    }
  }
}