#![cfg(all(feature = "secure-open", unix))]
use crate::{Jail, JailError, JailedPath};
use std::fs::{File, OpenOptions};
use std::io;
use std::os::unix::fs::OpenOptionsExt;
use std::path::Path;
#[cfg(target_os = "linux")]
const O_NOFOLLOW: i32 = 0o0400000;
#[cfg(target_os = "macos")]
const O_NOFOLLOW: i32 = 0x0100;
#[cfg(target_os = "freebsd")]
const O_NOFOLLOW: i32 = 0x0100;
#[cfg(target_os = "openbsd")]
const O_NOFOLLOW: i32 = 0x0100;
#[cfg(target_os = "netbsd")]
const O_NOFOLLOW: i32 = 0x0100;
#[cfg(target_os = "dragonfly")]
const O_NOFOLLOW: i32 = 0x0100;
#[cfg(not(any(
target_os = "linux",
target_os = "macos",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
)))]
compile_error!(
"path_jail secure-open: O_NOFOLLOW is not known for this Unix platform. \
Setting it to 0 would silently follow symlinks and defeat the feature's \
purpose. Please open an issue at https://github.com/tenuo-ai/path_jail \
with your target triple and the correct O_NOFOLLOW value from your \
system headers."
);
#[derive(Debug)]
pub struct JailedFile {
inner: File,
}
impl JailedFile {
#[inline]
pub fn into_inner(self) -> File {
self.inner
}
}
impl std::ops::Deref for JailedFile {
type Target = File;
#[inline]
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl std::ops::DerefMut for JailedFile {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl io::Read for JailedFile {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
}
impl io::Write for JailedFile {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(buf)
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
impl io::Seek for JailedFile {
#[inline]
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
self.inner.seek(pos)
}
}
impl Jail {
pub fn open<P: AsRef<Path>>(&self, relative: P) -> Result<JailedFile, JailError> {
let path = self.join(relative)?;
let file = OpenOptions::new()
.read(true)
.custom_flags(O_NOFOLLOW)
.open(&path)?;
Ok(JailedFile { inner: file })
}
pub fn create<P: AsRef<Path>>(&self, relative: P) -> Result<JailedFile, JailError> {
let path = self.join(relative)?;
let file = OpenOptions::new()
.write(true)
.create_new(true) .custom_flags(O_NOFOLLOW)
.open(&path)?;
Ok(JailedFile { inner: file })
}
pub fn create_or_truncate<P: AsRef<Path>>(&self, relative: P) -> Result<JailedFile, JailError> {
let path = self.join(relative)?;
let file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.custom_flags(O_NOFOLLOW)
.open(&path)?;
Ok(JailedFile { inner: file })
}
pub fn open_append<P: AsRef<Path>>(&self, relative: P) -> Result<JailedFile, JailError> {
let path = self.join(relative)?;
let file = OpenOptions::new()
.append(true)
.create(true)
.custom_flags(O_NOFOLLOW)
.open(&path)?;
Ok(JailedFile { inner: file })
}
}
impl JailedPath {
pub fn open(&self) -> Result<JailedFile, JailError> {
let file = OpenOptions::new()
.read(true)
.custom_flags(O_NOFOLLOW)
.open(self.as_path())?;
Ok(JailedFile { inner: file })
}
pub fn create(&self) -> Result<JailedFile, JailError> {
let file = OpenOptions::new()
.write(true)
.create_new(true)
.custom_flags(O_NOFOLLOW)
.open(self.as_path())?;
Ok(JailedFile { inner: file })
}
}