1
0
mirror of https://github.com/sharkdp/bat synced 2026-07-31 18:21:43 +00:00

Merge branch 'master' into makefile-syntax-for-justfiles

This commit is contained in:
Keith Hall
2026-05-02 13:56:45 +03:00
committed by GitHub
16 changed files with 132 additions and 49 deletions
+11 -5
View File
@@ -158,7 +158,9 @@ impl HighlightingAssets {
let syntax_match = mapping.get_syntax_for(path);
if let Some(MappingTarget::MapToUnknown) = syntax_match {
return Err(Error::UndetectedSyntax(path.to_string_lossy().into()));
return Err(Error::UndetectedSyntax(
crate::preprocessor::sanitize_for_terminal(&path.to_string_lossy()),
));
}
if let Some(MappingTarget::MapTo(syntax_name)) = syntax_match {
@@ -175,13 +177,17 @@ impl HighlightingAssets {
) {
(Some(syntax), _) => Ok(syntax),
(_, Some(MappingTarget::MapExtensionToUnknown)) => {
Err(Error::UndetectedSyntax(path.to_string_lossy().into()))
}
(_, Some(MappingTarget::MapExtensionToUnknown)) => Err(Error::UndetectedSyntax(
crate::preprocessor::sanitize_for_terminal(&path.to_string_lossy()),
)),
_ => self
.get_syntax_for_file_extension(file_name, &mapping.ignored_suffixes)?
.ok_or_else(|| Error::UndetectedSyntax(path.to_string_lossy().into())),
.ok_or_else(|| {
Error::UndetectedSyntax(crate::preprocessor::sanitize_for_terminal(
&path.to_string_lossy(),
))
}),
}
}
+1 -1
View File
@@ -645,7 +645,7 @@ impl App {
Ok(styled_components)
}
fn theme_options(&self) -> ThemeOptions {
pub(crate) fn theme_options(&self) -> ThemeOptions {
Self::theme_options_from_matches(&self.matches)
}
+7 -6
View File
@@ -17,7 +17,6 @@ use std::path::Path;
use std::process;
use bat::output::{OutputHandle, OutputType};
use bat::theme::DetectColorScheme;
use nu_ansi_term::Color::Green;
use nu_ansi_term::Style;
@@ -39,7 +38,7 @@ use bat::{
error::*,
input::Input,
style::{StyleComponent, StyleComponents},
theme::{color_scheme, default_theme, ColorScheme},
theme::{default_theme, theme, ColorScheme, ThemeOptions},
MappingTarget, PagingMode,
};
@@ -197,7 +196,7 @@ pub fn list_themes(
cfg: &Config,
config_dir: &Path,
cache_dir: &Path,
detect_color_scheme: DetectColorScheme,
theme_options: ThemeOptions,
) -> Result<()> {
let assets = assets_from_cache_or_binary(cfg.use_custom_assets, cache_dir)?;
let mut config = cfg.clone();
@@ -206,7 +205,7 @@ pub fn list_themes(
config.language = Some("Rust");
config.style_components = StyleComponents(style);
let default_theme_name = default_theme(color_scheme(detect_color_scheme).unwrap_or_default());
let default_theme_name = theme(theme_options).to_string();
let mut buf = String::new();
let mut handle = OutputHandle::FmtWrite(&mut buf);
@@ -259,7 +258,9 @@ pub fn list_themes(
fn set_terminal_title_to(new_terminal_title: String) {
let osc_command_for_setting_terminal_title = "\x1b]0;";
let osc_end_command = "\x07";
print!("{osc_command_for_setting_terminal_title}{new_terminal_title}{osc_end_command}");
// Prevent BEL/ESC/C1 bytes in the title from terminating or nesting the OSC.
let safe_title = bat::sanitize_for_terminal(&new_terminal_title);
print!("{osc_command_for_setting_terminal_title}{safe_title}{osc_end_command}");
io::stdout().flush().unwrap();
}
@@ -426,7 +427,7 @@ fn run() -> Result<bool> {
};
run_controller(inputs, &plain_config, cache_dir)
} else if app.matches.get_flag("list-themes") {
list_themes(&config, config_dir, cache_dir, DetectColorScheme::default())?;
list_themes(&config, config_dir, cache_dir, app.theme_options())?;
Ok(true)
} else if app.matches.get_flag("config-file") {
println!("{}", config_file().to_string_lossy());
+8 -8
View File
@@ -216,20 +216,20 @@ impl<'a> Input<'a> {
description,
metadata: self.metadata,
reader: {
let mut file = File::open(&path)
.map_err(|e| format!("'{}': {e}", path.to_string_lossy()))?;
let path_display =
crate::preprocessor::sanitize_for_terminal(&path.to_string_lossy());
let mut file =
File::open(&path).map_err(|e| format!("'{path_display}': {e}"))?;
if file.metadata()?.is_dir() {
return Err(format!("'{}' is a directory.", path.to_string_lossy()).into());
return Err(format!("'{path_display}' is a directory.").into());
}
if let Some(stdout) = stdout_identifier {
let input_identifier = Identifier::try_from(file).map_err(|e| {
format!("{}: Error identifying file: {e}", path.to_string_lossy())
})?;
let input_identifier = Identifier::try_from(file)
.map_err(|e| format!("{path_display}: Error identifying file: {e}"))?;
if stdout.surely_conflicts_with(&input_identifier) {
return Err(format!(
"IO circle detected. The input from '{}' is also an output. Aborting to avoid infinite loop.",
path.to_string_lossy()
"IO circle detected. The input from '{path_display}' is also an output. Aborting to avoid infinite loop.",
)
.into());
}
+1 -1
View File
@@ -261,7 +261,7 @@ impl Drop for Preprocessed {
}
};
if lessclose_output.status.success() {
if !lessclose_output.status.success() {
bat_warning!("$LESSCLOSE exited with nonzero exit code",)
};
}
+1
View File
@@ -54,6 +54,7 @@ mod vscreen;
pub(crate) mod wrapping;
pub use nonprintable_notation::{BinaryBehavior, NonprintableNotation};
pub use preprocessor::sanitize_for_terminal;
pub use preprocessor::StripAnsiMode;
pub use pretty_printer::{Input, PrettyPrinter, Syntax};
pub use syntax_mapping::{MappingTarget, SyntaxMapping};
+66
View File
@@ -149,6 +149,35 @@ pub fn strip_ansi(line: &str) -> String {
buffer
}
/// Escape C0, DEL, and C1 control characters so a string from an untrusted
/// filename or path can be safely written to the terminal.
pub fn sanitize_for_terminal(input: &str) -> String {
if !input
.chars()
.any(|c| matches!(c, '\x00'..='\x08' | '\x0A'..='\x1F' | '\x7F'..='\u{9F}'))
{
return input.to_owned();
}
let mut out = String::with_capacity(input.len() + 8);
for c in input.chars() {
match c {
'\t' => out.push('\t'),
'\x00'..='\x1F' => {
out.push('^');
out.push(char::from_u32(0x40 + c as u32).unwrap_or('?'));
}
'\x7F' => out.push_str("^?"),
'\u{80}'..='\u{9F}' => {
use std::fmt::Write as _;
let _ = write!(out, "\\u{{{:x}}}", c as u32);
}
other => out.push(other),
}
}
out
}
/// Strips overstrike sequences (backspace formatting) from input.
///
/// Overstrike formatting is used by man pages and some help output:
@@ -261,3 +290,40 @@ fn test_strip_overstrike() {
// Unicode with overstrike
assert_eq!(strip_overstrike("ä\x08äöü", 2), "äöü");
}
#[test]
fn test_sanitize_for_terminal_passthrough() {
assert_eq!(sanitize_for_terminal(""), "");
assert_eq!(sanitize_for_terminal("hello.txt"), "hello.txt");
assert_eq!(sanitize_for_terminal("résumé.pdf"), "résumé.pdf");
assert_eq!(sanitize_for_terminal("日本語.md"), "日本語.md");
assert_eq!(
sanitize_for_terminal("path/with spaces/file.log"),
"path/with spaces/file.log"
);
assert_eq!(sanitize_for_terminal("a\tb"), "a\tb");
}
#[test]
fn test_sanitize_for_terminal_c0_controls() {
assert_eq!(
sanitize_for_terminal("\x1b[31mINJECTED\x1b[0m.txt"),
"^[[31mINJECTED^[[0m.txt"
);
assert_eq!(sanitize_for_terminal("bad\x07rest"), "bad^Grest");
assert_eq!(sanitize_for_terminal("\x00\x08\n\r\x7F"), "^@^H^J^M^?");
assert_eq!(sanitize_for_terminal("\u{9b}31m"), "\\u{9b}31m");
assert_eq!(
sanitize_for_terminal("\u{9d}0;pwned\x07"),
"\\u{9d}0;pwned^G"
);
}
#[test]
fn test_sanitize_for_terminal_idempotent_on_sanitized() {
let dirty = "\x1b]0;pwned\x07file.txt";
let clean = sanitize_for_terminal(dirty);
assert_eq!(sanitize_for_terminal(&clean), clean);
assert!(!clean.contains('\x1b'));
assert!(!clean.contains('\x07'));
}
+8 -4
View File
@@ -29,7 +29,9 @@ use crate::error::*;
use crate::input::OpenedInput;
use crate::line_range::{MaxBufferedLineNumber, RangeCheckResult};
use crate::output::OutputHandle;
use crate::preprocessor::{expand_tabs, replace_nonprintable, strip_ansi, strip_overstrike};
use crate::preprocessor::{
expand_tabs, replace_nonprintable, sanitize_for_terminal, strip_ansi, strip_overstrike,
};
use crate::style::StyleComponent;
use crate::terminal::{as_terminal_escaped, to_ansi_color};
use crate::vscreen::{AnsiStyle, EscapeSequence, EscapeSequenceIterator};
@@ -489,7 +491,7 @@ impl Printer for InteractivePrinter<'_> {
(but will be present if the output of 'bat' is piped). You can use 'bat -A' \
to show the binary file contents.",
Yellow.paint("[bat warning]"),
input.description.summary(),
sanitize_for_terminal(&input.description.summary()),
)?;
} else if self.config.style_components.grid() {
self.print_horizontal_line(handle, '┬')?;
@@ -543,9 +545,11 @@ impl Printer for InteractivePrinter<'_> {
"{}{}{mode}",
description
.kind()
.map(|kind| format!("{kind}: "))
.map(|kind| format!("{}: ", sanitize_for_terminal(kind)))
.unwrap_or_else(|| "".into()),
self.colors.header_value.paint(description.title()),
self.colors
.header_value
.paint(sanitize_for_terminal(description.title())),
);
self.print_header_multiline_component(handle, &header_filename)
}