Skip to content

Fix expansion of ~ in path e nont in name #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Rust

on: [push]

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
150 changes: 95 additions & 55 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,67 +2,107 @@ extern crate libc;

use libc::{c_char, c_int, size_t};

extern
{
//fn getpwuid_r(uid: uid_t, pwd: *mut libc::passwd, buf: *mut c_char, buflen: size_t, result: *mut *mut libc::passwd) -> c_int
fn getpwnam_r(name: *const c_char, pwd: *mut libc::passwd, buf: *mut c_char, buflen: size_t, result: *mut *mut libc::passwd) -> c_int;
extern "C" {
//fn getpwuid_r(uid: uid_t, pwd: *mut libc::passwd, buf: *mut c_char, buflen: size_t, result: *mut *mut libc::passwd) -> c_int
fn getpwnam_r(
name: *const c_char,
pwd: *mut libc::passwd,
buf: *mut c_char,
buflen: size_t,
result: *mut *mut libc::passwd,
) -> c_int;
}

fn write_user_home<W: std::io::Write>(mut writer: W, username: &[u8]) -> Result<usize, std::io::Error>
{
let mut getpw_string_buf = [0; 4096];
let mut passwd: libc::passwd = unsafe {std::mem::zeroed()};
let mut passwd_out: *mut libc::passwd = std::ptr::null_mut();
let result = if username == &[]
{
let uid = unsafe {libc::getuid()};
unsafe {libc::getpwuid_r(uid, &mut passwd as *mut _,
getpw_string_buf.as_mut_ptr(), getpw_string_buf.len() as size_t,
&mut passwd_out as *mut _)}
}
else
{
let username = match std::ffi::CString::new(username)
{
Ok(un) => un,
Err(_) => return Err(std::io::Error::from_raw_os_error(libc::ENOENT)),
};
unsafe {getpwnam_r(username.as_ptr(), &mut passwd as *mut _,
getpw_string_buf.as_mut_ptr(), getpw_string_buf.len() as size_t,
&mut passwd_out as *mut _)}
};
if result == 0 {
writer.write(unsafe {std::ffi::CStr::from_ptr(passwd.pw_dir)}.to_bytes())
}
else
{
Err(std::io::Error::from_raw_os_error(result))
}
fn write_user_home<W: std::io::Write>(
mut writer: W,
username: &[u8],
) -> Result<usize, std::io::Error> {
let mut getpw_string_buf = [0; 4096];
let mut passwd: libc::passwd = unsafe { std::mem::zeroed() };
let mut passwd_out: *mut libc::passwd = std::ptr::null_mut();
let result = if username.is_empty() {
let uid = unsafe { libc::getuid() };
unsafe {
libc::getpwuid_r(
uid,
&mut passwd as *mut _,
getpw_string_buf.as_mut_ptr(),
getpw_string_buf.len() as size_t,
&mut passwd_out as *mut _,
)
}
} else {
let username = match std::ffi::CString::new(username) {
Ok(un) => un,
Err(_) => return Err(std::io::Error::from_raw_os_error(libc::ENOENT)),
};
unsafe {
getpwnam_r(
username.as_ptr(),
&mut passwd as *mut _,
getpw_string_buf.as_mut_ptr(),
getpw_string_buf.len() as size_t,
&mut passwd_out as *mut _,
)
}
};
if result == 0 {
writer.write(unsafe { std::ffi::CStr::from_ptr(passwd.pw_dir) }.to_bytes())
} else {
Err(std::io::Error::from_raw_os_error(result))
}
}

/// perform tilde-expansion, replacing an initial ~ or ~username with that username's home directory as determined by getpwnam
pub fn tilde_expand(s: &[u8]) -> Vec<u8>
{
let mut out = Vec::with_capacity(s.len());
/* if it starts with ~ and has no other tildes, tilde-expand it */
match s.first()
{
Some(&b'~') if s.iter().filter(|&&c| c==b'~').count() == 1 => {
let end = s.iter().position(|&c| c==b'/').unwrap_or(s.len());
let name = &s[1..end];
let _ = write_user_home(&mut out, name);
out.extend_from_slice(&s[end..]);
},
_ => out.extend_from_slice(s)
}
out
pub fn tilde_expand(s: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(s.len());
/* if it starts with ~ and has no other tildes before /, tilde-expand it */
let maybe_name = if s.starts_with(b"~") {
let end = s.iter().position(|&c| b'/' == c).unwrap_or_else(|| s.len());
let name = &s[1..end];
let rest = &s[end..];
if name.contains(&b'~') {
None
} else {
Some((name, rest))
}
} else {
None
};
if let Some((name, rest)) = maybe_name {
let _ = write_user_home(&mut out, name);
out.extend_from_slice(rest);
} else {
out.extend_from_slice(s)
}
out
}

#[test]
fn test()
{
println!("{}", String::from_utf8_lossy(&*tilde_expand(b"~/user-test")));
println!("{}", String::from_utf8_lossy(&*tilde_expand(b"~root/root-test")));
println!("{}", String::from_utf8_lossy(&*tilde_expand(b"noexpand~/test")));
println!("{}", String::from_utf8_lossy(&*tilde_expand(b"~~/noexpand-test")));
fn test_output_equals_bash() {
use std::process::Command;
fn bash(path: &[u8]) -> Vec<u8> {
Command::new("sh")
.arg("-c")
.arg(format!("echo -n {}", String::from_utf8_lossy(path)))
.output()
.expect("failed to execute process")
.stdout
}
fn check_output_equals(path: &[u8]) {
let internal = tilde_expand(path);
let reference = bash(path);
assert_eq!(
internal,
reference,
"'{}' differs from expected '{}'",
String::from_utf8_lossy(&internal),
String::from_utf8_lossy(&reference)
);
}
check_output_equals(b"~/user-test");
check_output_equals(b"~root/root-test");
check_output_equals(b"~root/root~test");
check_output_equals(b"noexpand~/test");
check_output_equals(b"~~/noexpand-test");
}