Skip to main content

pray_core/
render_file.rs

1use crate::{PrayError, PrayResult};
2use std::fs::{self, File, OpenOptions};
3use std::io::{ErrorKind, Read, Write};
4use std::path::Path;
5
6pub(crate) fn create_regular_bytes(path: &Path, display: &str, bytes: &[u8]) -> PrayResult<()> {
7    if crate::transaction::replace(path, None, Some(bytes))? {
8        return Ok(());
9    }
10    let mut options = OpenOptions::new();
11    options.write(true).create_new(true);
12    add_no_follow(&mut options);
13    let mut file = options
14        .open(path)
15        .map_err(|error| map_open_error(error, display))?;
16    file.write_all(bytes)?;
17    Ok(())
18}
19
20pub(crate) fn read_regular_bytes(path: &Path, display: &str) -> PrayResult<Vec<u8>> {
21    let mut file = open_regular(path, display, false)?;
22    read_destination_bytes(&mut file, display)
23}
24
25pub fn read_destination_text(path: &Path) -> PrayResult<String> {
26    String::from_utf8(read_regular_bytes(path, &path.display().to_string())?)
27        .map_err(|error| PrayError::Render(error.to_string()))
28}
29
30pub(crate) const MAX_DESTINATION_BYTES: u64 = 32 * 1024 * 1024;
31
32pub(crate) fn read_destination_bytes(file: &mut File, display: &str) -> PrayResult<Vec<u8>> {
33    if file.metadata()?.len() > MAX_DESTINATION_BYTES {
34        return Err(destination_size_error(display));
35    }
36    let mut bytes = Vec::new();
37    file.take(MAX_DESTINATION_BYTES + 1)
38        .read_to_end(&mut bytes)?;
39    if bytes.len() as u64 > MAX_DESTINATION_BYTES {
40        return Err(destination_size_error(display));
41    }
42    Ok(bytes)
43}
44
45fn destination_size_error(display: &str) -> PrayError {
46    PrayError::Render(format!(
47        "refusing to read `{display}`; destination exceeds the 32 MiB limit"
48    ))
49}
50
51pub(crate) fn open_regular(path: &Path, display: &str, writable: bool) -> PrayResult<File> {
52    let mut options = OpenOptions::new();
53    options.read(true).write(writable);
54    add_no_follow(&mut options);
55    let file = options
56        .open(path)
57        .map_err(|error| map_open_error(error, display))?;
58    if !file.metadata()?.is_file() {
59        return Err(PrayError::Render(format!(
60            "refusing to write `{display}`; destination is not a regular file"
61        )));
62    }
63    Ok(file)
64}
65
66#[cfg(unix)]
67fn add_no_follow(options: &mut OpenOptions) {
68    use std::os::unix::fs::OpenOptionsExt;
69    options.custom_flags(libc::O_NOFOLLOW);
70}
71
72#[cfg(not(unix))]
73fn add_no_follow(_options: &mut OpenOptions) {}
74
75fn map_open_error(error: std::io::Error, display: &str) -> PrayError {
76    #[cfg(unix)]
77    if error.raw_os_error() == Some(libc::ELOOP) {
78        return symlink_error(display);
79    }
80    error.into()
81}
82
83pub(crate) fn symlink_error(display: &str) -> PrayError {
84    PrayError::Render(format!(
85        "refusing to write `{display}` because it is a symbolic link"
86    ))
87}
88
89pub(crate) enum DestinationKind {
90    Missing,
91    Regular,
92    Symlink,
93    Other,
94}
95
96pub(crate) fn destination_kind(path: &Path) -> PrayResult<DestinationKind> {
97    match fs::symlink_metadata(path) {
98        Ok(metadata) if metadata.file_type().is_symlink() => Ok(DestinationKind::Symlink),
99        Ok(metadata) if metadata.is_file() => Ok(DestinationKind::Regular),
100        Ok(_) => Ok(DestinationKind::Other),
101        Err(error) if error.kind() == ErrorKind::NotFound => Ok(DestinationKind::Missing),
102        Err(error) => Err(error.into()),
103    }
104}