summaryrefslogtreecommitdiff
path: root/crates/fs-mistrust/src/testing.rs
blob: a2811f6c0e4e2de1edfd3161a9b9f7ffe94ecca3 (plain)
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
//! Testing support functions, to more easily make a bunch of directories and
//! links.
//!
//! This module is only built when compiling tests.

use std::{
    fs::{self, File},
    io::Write,
    path::{Path, PathBuf},
};

#[cfg(target_family = "unix")]
use std::os::unix::{self, fs::PermissionsExt};

use crate::Mistrust;

/// A temporary directory with convenience functions to build items inside it.
#[derive(Debug)]
pub(crate) struct Dir {
    /// The temporary directory
    toplevel: tempfile::TempDir,
    /// Canonicalized path to the temporary directory
    canonical_root: PathBuf,
}

/// When creating a link, are we creating a directory link or a file link?
///
/// (These are the same on Unix, and different on windows.)
#[cfg(target_family = "unix")]
#[derive(Copy, Clone, Debug)]
pub(crate) enum LinkType {
    Dir,
    File,
}

impl Dir {
    /// Make a new temporary directory
    pub(crate) fn new() -> Self {
        let toplevel = tempfile::TempDir::new().expect("Can't get tempfile");
        let canonical_root = toplevel.path().canonicalize().expect("Can't canonicalize");

        Dir {
            toplevel,
            canonical_root,
        }
    }

    /// Return the canonical path of the directory's root.
    pub(crate) fn canonical_root(&self) -> &Path {
        self.canonical_root.as_path()
    }

    /// Return the path to the temporary directory's root relative to our working directory.
    pub(crate) fn relative_root(&self) -> PathBuf {
        let mut cwd = std::env::current_dir().expect("no cwd");
        let mut relative = PathBuf::new();
        // TODO(nickm): I am reasonably confident that this will not work
        // correctly on windows.
        while !self.toplevel.path().starts_with(&cwd) {
            assert!(cwd.pop());
            relative.push("..");
        }
        relative.join(
            self.toplevel
                .path()
                .strip_prefix(cwd)
                .expect("error computing common ancestor"),
        )
    }

    /// Return the path of `p` within this temporary directory.
    ///
    /// Requires that `p` is a relative path.
    pub(crate) fn path(&self, p: impl AsRef<Path>) -> PathBuf {
        let p = p.as_ref();
        assert!(p.is_relative());
        self.canonical_root.join(p)
    }

    /// Make a  directory at `p` within this temporary directory, creating
    /// parent directories as needed.
    ///
    /// Requires that `p` is a relative path.
    pub(crate) fn dir(&self, p: impl AsRef<Path>) {
        fs::create_dir_all(self.path(p)).expect("Can't create directory.");
    }

    /// Make a small file at `p` within this temporary directory, creating
    /// parent directories as needed.
    ///
    /// Requires that `p` is a relative path.
    pub(crate) fn file(&self, p: impl AsRef<Path>) {
        self.dir(p.as_ref().parent().expect("Tempdir had no parent"));
        let mut f = File::create(self.path(p)).expect("Can't create file");
        f.write_all(&b"This space is intentionally left blank"[..])
            .expect("Can't write");
    }

    /// Make a relative link from "original" to "link" within this temporary
    /// directory, where `original` is relative
    /// to the directory containing `link`, and `link` is relative to the temporary directory.
    #[cfg(target_family = "unix")]
    pub(crate) fn link_rel(
        &self,
        link_type: LinkType,
        original: impl AsRef<Path>,
        link: impl AsRef<Path>,
    ) {
        {
            let _ = link_type;
            unix::fs::symlink(original.as_ref(), self.path(link)).expect("Can't symlink");
        }

        // Windows does support symlinks but it requires elevated privileges. For more information,
        // please have a look at:
        // https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/create-symbolic-links
    }

    /// As `link_rel`, but create an absolute link.  `original` is now relative
    /// to the temporary directory.
    #[cfg(target_family = "unix")]
    pub(crate) fn link_abs(
        &self,
        link_type: LinkType,
        original: impl AsRef<Path>,
        link: impl AsRef<Path>,
    ) {
        self.link_rel(link_type, self.path(original), link);
    }

    /// Change the unix permissions of a file.
    ///
    /// Requires that `p` is a relative path.
    ///
    /// Does nothing on windows.
    pub(crate) fn chmod(&self, p: impl AsRef<Path>, mode: u32) {
        #[cfg(target_family = "unix")]
        {
            let perm = fs::Permissions::from_mode(mode);
            fs::set_permissions(self.path(p), perm).expect("can't chmod");
        }
        #[cfg(not(target_family = "unix"))]
        {
            let (_, _) = (p, mode);
        }
    }
}

/// A utility type to represent the different operations available for a MistrustBuilder.
#[derive(Debug)]
pub(crate) enum MistrustOp<'a> {
    IgnorePrefix(&'a Path),
    DangerouslyTrustEveryone(),
    TrustNoGroupId(),

    #[cfg(target_family = "unix")]
    TrustAdminOnly(),

    #[cfg(target_family = "unix")]
    TrustGroup(u32),
}

/// A convenience function to construct a Mistrust type using a set of given operations.
pub(crate) fn mistrust_build(ops: &[MistrustOp]) -> Mistrust {
    ops.iter()
        .fold(&mut Mistrust::builder(), |m, op| {
            match op {
                MistrustOp::IgnorePrefix(prefix) => m.ignore_prefix(prefix),

                MistrustOp::DangerouslyTrustEveryone() => m.dangerously_trust_everyone(),

                MistrustOp::TrustNoGroupId() => {
                    // We call `m.trust_no_group_id()` on platforms where it is available.
                    // Otherwise, we simply return `m` unmodified here.
                    #[cfg(all(
                        target_family = "unix",
                        not(target_os = "ios"),
                        not(target_os = "android"),
                        not(target_os = "tvos")
                    ))]
                    return m.trust_no_group_id();

                    #[cfg(not(all(
                        target_family = "unix",
                        not(target_os = "ios"),
                        not(target_os = "android"),
                        not(target_os = "tvos")
                    )))]
                    return m;
                }

                #[cfg(target_family = "unix")]
                MistrustOp::TrustAdminOnly() => m.trust_admin_only(),

                #[cfg(target_family = "unix")]
                MistrustOp::TrustGroup(gid) => m.trust_group(*gid),
            }
        })
        .build()
        .expect("Unable to build Mistrust object")
}