mirror of
https://github.com/BurntSushi/ripgrep.git
synced 2025-07-27 02:01:58 -07:00
Compare commits
1 Commits
grep-searc
...
ag/fix-cve
Author | SHA1 | Date | |
---|---|---|---|
|
aecc0ea126 |
@@ -1,7 +1,7 @@
|
|||||||
use std::ffi::{OsStr, OsString};
|
use std::ffi::{OsStr, OsString};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
@@ -24,7 +24,7 @@ struct DecompressionCommand {
|
|||||||
/// The glob that matches this command.
|
/// The glob that matches this command.
|
||||||
glob: String,
|
glob: String,
|
||||||
/// The command or binary name.
|
/// The command or binary name.
|
||||||
bin: OsString,
|
bin: PathBuf,
|
||||||
/// The arguments to invoke with the command.
|
/// The arguments to invoke with the command.
|
||||||
args: Vec<OsString>,
|
args: Vec<OsString>,
|
||||||
}
|
}
|
||||||
@@ -83,23 +83,60 @@ impl DecompressionMatcherBuilder {
|
|||||||
///
|
///
|
||||||
/// The syntax for the glob is documented in the
|
/// The syntax for the glob is documented in the
|
||||||
/// [`globset` crate](https://docs.rs/globset/#syntax).
|
/// [`globset` crate](https://docs.rs/globset/#syntax).
|
||||||
|
///
|
||||||
|
/// The `program` given is resolved with respect to `PATH` and turned
|
||||||
|
/// into an absolute path internally before being executed by the current
|
||||||
|
/// platform. Notably, on Windows, this avoids a security problem where
|
||||||
|
/// passing a relative path to `CreateProcess` will automatically search
|
||||||
|
/// the current directory for a matching program. If the program could
|
||||||
|
/// not be resolved, then it is silently ignored and the association is
|
||||||
|
/// dropped. For this reason, callers should prefer `try_associate`.
|
||||||
pub fn associate<P, I, A>(
|
pub fn associate<P, I, A>(
|
||||||
&mut self,
|
&mut self,
|
||||||
glob: &str,
|
glob: &str,
|
||||||
program: P,
|
program: P,
|
||||||
args: I,
|
args: I,
|
||||||
) -> &mut DecompressionMatcherBuilder
|
) -> &mut DecompressionMatcherBuilder
|
||||||
|
where
|
||||||
|
P: AsRef<OsStr>,
|
||||||
|
I: IntoIterator<Item = A>,
|
||||||
|
A: AsRef<OsStr>,
|
||||||
|
{
|
||||||
|
let _ = self.try_associate(glob, program, args);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Associates a glob with a command to decompress files matching the glob.
|
||||||
|
///
|
||||||
|
/// If multiple globs match the same file, then the most recently added
|
||||||
|
/// glob takes precedence.
|
||||||
|
///
|
||||||
|
/// The syntax for the glob is documented in the
|
||||||
|
/// [`globset` crate](https://docs.rs/globset/#syntax).
|
||||||
|
///
|
||||||
|
/// The `program` given is resolved with respect to `PATH` and turned
|
||||||
|
/// into an absolute path internally before being executed by the current
|
||||||
|
/// platform. Notably, on Windows, this avoids a security problem where
|
||||||
|
/// passing a relative path to `CreateProcess` will automatically search
|
||||||
|
/// the current directory for a matching program. If the program could not
|
||||||
|
/// be resolved, then an error is returned.
|
||||||
|
pub fn try_associate<P, I, A>(
|
||||||
|
&mut self,
|
||||||
|
glob: &str,
|
||||||
|
program: P,
|
||||||
|
args: I,
|
||||||
|
) -> Result<&mut DecompressionMatcherBuilder, CommandError>
|
||||||
where
|
where
|
||||||
P: AsRef<OsStr>,
|
P: AsRef<OsStr>,
|
||||||
I: IntoIterator<Item = A>,
|
I: IntoIterator<Item = A>,
|
||||||
A: AsRef<OsStr>,
|
A: AsRef<OsStr>,
|
||||||
{
|
{
|
||||||
let glob = glob.to_string();
|
let glob = glob.to_string();
|
||||||
let bin = program.as_ref().to_os_string();
|
let bin = resolve_binary(Path::new(program.as_ref()))?;
|
||||||
let args =
|
let args =
|
||||||
args.into_iter().map(|a| a.as_ref().to_os_string()).collect();
|
args.into_iter().map(|a| a.as_ref().to_os_string()).collect();
|
||||||
self.commands.push(DecompressionCommand { glob, bin, args });
|
self.commands.push(DecompressionCommand { glob, bin, args });
|
||||||
self
|
Ok(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,6 +377,70 @@ impl io::Read for DecompressionReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves a path to a program to a path by searching for the program in
|
||||||
|
/// `PATH`.
|
||||||
|
///
|
||||||
|
/// If the program could not be resolved, then an error is returned.
|
||||||
|
///
|
||||||
|
/// The purpose of doing this instead of passing the path to the program
|
||||||
|
/// directly to Command::new is that Command::new will hand relative paths
|
||||||
|
/// to CreateProcess on Windows, which will implicitly search the current
|
||||||
|
/// working directory for the executable. This could be undesirable for
|
||||||
|
/// security reasons. e.g., running ripgrep with the -z/--search-zip flag on an
|
||||||
|
/// untrusted directory tree could result in arbitrary programs executing on
|
||||||
|
/// Windows.
|
||||||
|
///
|
||||||
|
/// Note that this could still return a relative path if PATH contains a
|
||||||
|
/// relative path. We permit this since it is assumed that the user has set
|
||||||
|
/// this explicitly, and thus, desires this behavior.
|
||||||
|
///
|
||||||
|
/// On non-Windows, this is a no-op.
|
||||||
|
pub fn resolve_binary<P: AsRef<Path>>(
|
||||||
|
prog: P,
|
||||||
|
) -> Result<PathBuf, CommandError> {
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
fn is_exe(path: &Path) -> bool {
|
||||||
|
let md = match path.metadata() {
|
||||||
|
Err(_) => return false,
|
||||||
|
Ok(md) => md,
|
||||||
|
};
|
||||||
|
!md.is_dir()
|
||||||
|
}
|
||||||
|
|
||||||
|
let prog = prog.as_ref();
|
||||||
|
if !cfg!(windows) || prog.is_absolute() {
|
||||||
|
return Ok(prog.to_path_buf());
|
||||||
|
}
|
||||||
|
let syspaths = match env::var_os("PATH") {
|
||||||
|
Some(syspaths) => syspaths,
|
||||||
|
None => {
|
||||||
|
let msg = "system PATH environment variable not found";
|
||||||
|
return Err(CommandError::io(io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
msg,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for syspath in env::split_paths(&syspaths) {
|
||||||
|
if syspath.as_os_str().is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let abs_prog = syspath.join(prog);
|
||||||
|
if is_exe(&abs_prog) {
|
||||||
|
return Ok(abs_prog.to_path_buf());
|
||||||
|
}
|
||||||
|
if abs_prog.extension().is_none() {
|
||||||
|
let abs_prog = abs_prog.with_extension("exe");
|
||||||
|
if is_exe(&abs_prog) {
|
||||||
|
return Ok(abs_prog.to_path_buf());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let msg = format!("{}: could not find executable in PATH", prog.display());
|
||||||
|
return Err(CommandError::io(io::Error::new(io::ErrorKind::Other, msg)));
|
||||||
|
}
|
||||||
|
|
||||||
fn default_decompression_commands() -> Vec<DecompressionCommand> {
|
fn default_decompression_commands() -> Vec<DecompressionCommand> {
|
||||||
const ARGS_GZIP: &[&str] = &["gzip", "-d", "-c"];
|
const ARGS_GZIP: &[&str] = &["gzip", "-d", "-c"];
|
||||||
const ARGS_BZIP: &[&str] = &["bzip2", "-d", "-c"];
|
const ARGS_BZIP: &[&str] = &["bzip2", "-d", "-c"];
|
||||||
@@ -350,29 +451,36 @@ fn default_decompression_commands() -> Vec<DecompressionCommand> {
|
|||||||
const ARGS_ZSTD: &[&str] = &["zstd", "-q", "-d", "-c"];
|
const ARGS_ZSTD: &[&str] = &["zstd", "-q", "-d", "-c"];
|
||||||
const ARGS_UNCOMPRESS: &[&str] = &["uncompress", "-c"];
|
const ARGS_UNCOMPRESS: &[&str] = &["uncompress", "-c"];
|
||||||
|
|
||||||
fn cmd(glob: &str, args: &[&str]) -> DecompressionCommand {
|
fn add(glob: &str, args: &[&str], cmds: &mut Vec<DecompressionCommand>) {
|
||||||
DecompressionCommand {
|
let bin = match resolve_binary(Path::new(args[0])) {
|
||||||
|
Ok(bin) => bin,
|
||||||
|
Err(err) => {
|
||||||
|
debug!("{}", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
cmds.push(DecompressionCommand {
|
||||||
glob: glob.to_string(),
|
glob: glob.to_string(),
|
||||||
bin: OsStr::new(&args[0]).to_os_string(),
|
bin,
|
||||||
args: args
|
args: args
|
||||||
.iter()
|
.iter()
|
||||||
.skip(1)
|
.skip(1)
|
||||||
.map(|s| OsStr::new(s).to_os_string())
|
.map(|s| OsStr::new(s).to_os_string())
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
vec![
|
let mut cmds = vec![];
|
||||||
cmd("*.gz", ARGS_GZIP),
|
add("*.gz", ARGS_GZIP, &mut cmds);
|
||||||
cmd("*.tgz", ARGS_GZIP),
|
add("*.tgz", ARGS_GZIP, &mut cmds);
|
||||||
cmd("*.bz2", ARGS_BZIP),
|
add("*.bz2", ARGS_BZIP, &mut cmds);
|
||||||
cmd("*.tbz2", ARGS_BZIP),
|
add("*.tbz2", ARGS_BZIP, &mut cmds);
|
||||||
cmd("*.xz", ARGS_XZ),
|
add("*.xz", ARGS_XZ, &mut cmds);
|
||||||
cmd("*.txz", ARGS_XZ),
|
add("*.txz", ARGS_XZ, &mut cmds);
|
||||||
cmd("*.lz4", ARGS_LZ4),
|
add("*.lz4", ARGS_LZ4, &mut cmds);
|
||||||
cmd("*.lzma", ARGS_LZMA),
|
add("*.lzma", ARGS_LZMA, &mut cmds);
|
||||||
cmd("*.br", ARGS_BROTLI),
|
add("*.br", ARGS_BROTLI, &mut cmds);
|
||||||
cmd("*.zst", ARGS_ZSTD),
|
add("*.zst", ARGS_ZSTD, &mut cmds);
|
||||||
cmd("*.zstd", ARGS_ZSTD),
|
add("*.zstd", ARGS_ZSTD, &mut cmds);
|
||||||
cmd("*.Z", ARGS_UNCOMPRESS),
|
add("*.Z", ARGS_UNCOMPRESS, &mut cmds);
|
||||||
]
|
cmds
|
||||||
}
|
}
|
||||||
|
@@ -179,8 +179,8 @@ mod process;
|
|||||||
mod wtr;
|
mod wtr;
|
||||||
|
|
||||||
pub use decompress::{
|
pub use decompress::{
|
||||||
DecompressionMatcher, DecompressionMatcherBuilder, DecompressionReader,
|
resolve_binary, DecompressionMatcher, DecompressionMatcherBuilder,
|
||||||
DecompressionReaderBuilder,
|
DecompressionReader, DecompressionReaderBuilder,
|
||||||
};
|
};
|
||||||
pub use escape::{escape, escape_os, unescape, unescape_os};
|
pub use escape::{escape, escape_os, unescape, unescape_os};
|
||||||
pub use human::{parse_human_readable_size, ParseSizeError};
|
pub use human::{parse_human_readable_size, ParseSizeError};
|
||||||
|
@@ -290,7 +290,7 @@ impl Args {
|
|||||||
let mut builder = SearchWorkerBuilder::new();
|
let mut builder = SearchWorkerBuilder::new();
|
||||||
builder
|
builder
|
||||||
.json_stats(matches.is_present("json"))
|
.json_stats(matches.is_present("json"))
|
||||||
.preprocessor(matches.preprocessor())
|
.preprocessor(matches.preprocessor())?
|
||||||
.preprocessor_globs(matches.preprocessor_globs()?)
|
.preprocessor_globs(matches.preprocessor_globs()?)
|
||||||
.search_zip(matches.is_present("search-zip"))
|
.search_zip(matches.is_present("search-zip"))
|
||||||
.binary_detection_implicit(matches.binary_detection_implicit())
|
.binary_detection_implicit(matches.binary_detection_implicit())
|
||||||
|
@@ -115,9 +115,14 @@ impl SearchWorkerBuilder {
|
|||||||
pub fn preprocessor(
|
pub fn preprocessor(
|
||||||
&mut self,
|
&mut self,
|
||||||
cmd: Option<PathBuf>,
|
cmd: Option<PathBuf>,
|
||||||
) -> &mut SearchWorkerBuilder {
|
) -> crate::Result<&mut SearchWorkerBuilder> {
|
||||||
self.config.preprocessor = cmd;
|
if let Some(ref prog) = cmd {
|
||||||
self
|
let bin = cli::resolve_binary(prog)?;
|
||||||
|
self.config.preprocessor = Some(bin);
|
||||||
|
} else {
|
||||||
|
self.config.preprocessor = None;
|
||||||
|
}
|
||||||
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the globs for determining which files should be run through the
|
/// Set the globs for determining which files should be run through the
|
||||||
|
Reference in New Issue
Block a user