Merge branch 'master' into makefile-syntax-for-justfiles
This commit is contained in:
16 files changed
+132
-49
No files matched your search
@@ -22,6 +22,9 @@
|
|||||||
- Syntax highlighting for Python files using uv as script runner in shebang #3689 (@janlarres)
|
- Syntax highlighting for Python files using uv as script runner in shebang #3689 (@janlarres)
|
||||||
|
|
||||||
## Bugfixes
|
## Bugfixes
|
||||||
|
- Fix `--list-themes` unconditionally probing the terminal via OSC 10/11 even when `--theme` was set to an explicit value, see #3700 (regression introduced in bc42149a). (@optimistiCli)
|
||||||
|
- Fix inverted `$LESSCLOSE` warning so bat warns on nonzero exit, not on success. See #3654 (@cuiweixie)
|
||||||
|
- Sanitize control characters in filenames before displaying them in the file header, error messages, and the terminal title, preventing ANSI escape injection via crafted filenames. Closes #3054, see #3691 (@curious-rabbit)
|
||||||
- Report initial input read errors instead of treating them as empty input. Closes #3002, see #3706 (@lawrence3699)
|
- Report initial input read errors instead of treating them as empty input. Closes #3002, see #3706 (@lawrence3699)
|
||||||
- Treat ZIP archives as binary content based on their magic header, see #3686 (@officialasishkumar)
|
- Treat ZIP archives as binary content based on their magic header, see #3686 (@officialasishkumar)
|
||||||
- Fix i686 `.deb` package using incorrect architecture name (`i686` instead of `i386`), preventing installation on Debian. Closes #3611, see #3650 (@Sim-hu)
|
- Fix i686 `.deb` package using incorrect architecture name (`i686` instead of `i386`), preventing installation on Debian. Closes #3611, see #3650 (@Sim-hu)
|
||||||
|
|||||||
@@ -19,20 +19,6 @@
|
|||||||
[<a href="doc/README-ru.md">Русский</a>]
|
[<a href="doc/README-ru.md">Русский</a>]
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
### Sponsors
|
|
||||||
|
|
||||||
A special *thank you* goes to our biggest <a href="doc/sponsors.md">sponsors</a>:<br>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="https://www.warp.dev/bat">
|
|
||||||
<img src="doc/sponsors/warp-logo.png" width="200" alt="Warp">
|
|
||||||
<br>
|
|
||||||
<strong>Warp, the intelligent terminal</strong>
|
|
||||||
<br>
|
|
||||||
<sub>Available on MacOS, Linux, Windows</sub>
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
### Syntax highlighting
|
### Syntax highlighting
|
||||||
|
|
||||||
`bat` supports syntax highlighting for a large number of programming and markup
|
`bat` supports syntax highlighting for a large number of programming and markup
|
||||||
@@ -208,12 +194,6 @@ Note that the [Manpage syntax](assets/syntaxes/02_Extra/Manpage.sublime-syntax)
|
|||||||
|
|
||||||
The [`prettybat`](https://github.com/eth-p/bat-extras/blob/master/doc/prettybat.md) script is a wrapper that will format code and print it with `bat`.
|
The [`prettybat`](https://github.com/eth-p/bat-extras/blob/master/doc/prettybat.md) script is a wrapper that will format code and print it with `bat`.
|
||||||
|
|
||||||
#### `Warp`
|
|
||||||
|
|
||||||
<a href="https://app.warp.dev/drive/folder/-Bat-Warp-Pack-lxhe7HrEwgwpG17mvrFSz1">
|
|
||||||
<img src="doc/sponsors/warp-pack-header.png" alt="Warp">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
#### Highlighting `--help` messages
|
#### Highlighting `--help` messages
|
||||||
|
|
||||||
You can use `bat` to colorize help text: `$ cp --help | bat -plhelp`
|
You can use `bat` to colorize help text: `$ cp --help | bat -plhelp`
|
||||||
@@ -527,6 +507,30 @@ and line numbers but no grid and no file header. Set the `BAT_STYLE` environment
|
|||||||
variable to make these changes permanent or use `bat`'s
|
variable to make these changes permanent or use `bat`'s
|
||||||
[configuration file](#configuration-file).
|
[configuration file](#configuration-file).
|
||||||
|
|
||||||
|
By default, `bat` enables `changes`, `grid`, `header-filename`, `numbers`, and `snip`.
|
||||||
|
|
||||||
|
The available pre-defined styles are:
|
||||||
|
|
||||||
|
| Style | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `default` | Enables the recommended style components listed above. |
|
||||||
|
| `full` | Enables all available components. |
|
||||||
|
| `auto` | Same as `default`, unless the output is piped. |
|
||||||
|
| `plain` | Disables all available components. |
|
||||||
|
|
||||||
|
The available individual components are:
|
||||||
|
|
||||||
|
| Component | Description |
|
||||||
|
|-----------|-------------|
|
||||||
|
| `changes` | Show Git modification markers. |
|
||||||
|
| `header` | Alias for `header-filename`. |
|
||||||
|
| `header-filename` | Show filenames before the content. |
|
||||||
|
| `header-filesize` | Show file sizes before the content. |
|
||||||
|
| `grid` | Vertical/horizontal lines to separate the side bar and header from the content. |
|
||||||
|
| `rule` | Horizontal lines to delimit files. |
|
||||||
|
| `numbers` | Show line numbers in the side bar. |
|
||||||
|
| `snip` | Draw separation lines between distinct line ranges. |
|
||||||
|
|
||||||
>[!tip]
|
>[!tip]
|
||||||
> If you specify a default style in `bat`'s config file, you can change which components
|
> If you specify a default style in `bat`'s config file, you can change which components
|
||||||
> are displayed during a single run of `bat` using the `--style` command-line argument.
|
> are displayed during a single run of `bat` using the `--style` command-line argument.
|
||||||
|
|||||||
Vendored
+1
-1
Submodule assets/syntaxes/02_Extra/PureScript updated: 5acebc1850...1773f4fddb.
+1
-1
Submodule assets/syntaxes/02_Extra/typst-syntax-highlight updated: 363f0e767c...5f71d12fa1.
@@ -10,5 +10,3 @@ No issue will have a different priority based on sponsorship status of the
|
|||||||
reporter.
|
reporter.
|
||||||
|
|
||||||
Contributions from anybody are most welcomed, please see our [`CONTRIBUTING.md`](../CONTRIBUTING.md) guide.
|
Contributions from anybody are most welcomed, please see our [`CONTRIBUTING.md`](../CONTRIBUTING.md) guide.
|
||||||
|
|
||||||
If you want to see our biggest sponsors, check the top of [`README.md`](../README.md#sponsors).
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 130 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 37 KiB |
+11
-5
@@ -158,7 +158,9 @@ impl HighlightingAssets {
|
|||||||
let syntax_match = mapping.get_syntax_for(path);
|
let syntax_match = mapping.get_syntax_for(path);
|
||||||
|
|
||||||
if let Some(MappingTarget::MapToUnknown) = syntax_match {
|
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 {
|
if let Some(MappingTarget::MapTo(syntax_name)) = syntax_match {
|
||||||
@@ -175,13 +177,17 @@ impl HighlightingAssets {
|
|||||||
) {
|
) {
|
||||||
(Some(syntax), _) => Ok(syntax),
|
(Some(syntax), _) => Ok(syntax),
|
||||||
|
|
||||||
(_, Some(MappingTarget::MapExtensionToUnknown)) => {
|
(_, Some(MappingTarget::MapExtensionToUnknown)) => Err(Error::UndetectedSyntax(
|
||||||
Err(Error::UndetectedSyntax(path.to_string_lossy().into()))
|
crate::preprocessor::sanitize_for_terminal(&path.to_string_lossy()),
|
||||||
}
|
)),
|
||||||
|
|
||||||
_ => self
|
_ => self
|
||||||
.get_syntax_for_file_extension(file_name, &mapping.ignored_suffixes)?
|
.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
@@ -645,7 +645,7 @@ impl App {
|
|||||||
Ok(styled_components)
|
Ok(styled_components)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn theme_options(&self) -> ThemeOptions {
|
pub(crate) fn theme_options(&self) -> ThemeOptions {
|
||||||
Self::theme_options_from_matches(&self.matches)
|
Self::theme_options_from_matches(&self.matches)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-6
@@ -17,7 +17,6 @@ use std::path::Path;
|
|||||||
use std::process;
|
use std::process;
|
||||||
|
|
||||||
use bat::output::{OutputHandle, OutputType};
|
use bat::output::{OutputHandle, OutputType};
|
||||||
use bat::theme::DetectColorScheme;
|
|
||||||
use nu_ansi_term::Color::Green;
|
use nu_ansi_term::Color::Green;
|
||||||
use nu_ansi_term::Style;
|
use nu_ansi_term::Style;
|
||||||
|
|
||||||
@@ -39,7 +38,7 @@ use bat::{
|
|||||||
error::*,
|
error::*,
|
||||||
input::Input,
|
input::Input,
|
||||||
style::{StyleComponent, StyleComponents},
|
style::{StyleComponent, StyleComponents},
|
||||||
theme::{color_scheme, default_theme, ColorScheme},
|
theme::{default_theme, theme, ColorScheme, ThemeOptions},
|
||||||
MappingTarget, PagingMode,
|
MappingTarget, PagingMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -197,7 +196,7 @@ pub fn list_themes(
|
|||||||
cfg: &Config,
|
cfg: &Config,
|
||||||
config_dir: &Path,
|
config_dir: &Path,
|
||||||
cache_dir: &Path,
|
cache_dir: &Path,
|
||||||
detect_color_scheme: DetectColorScheme,
|
theme_options: ThemeOptions,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let assets = assets_from_cache_or_binary(cfg.use_custom_assets, cache_dir)?;
|
let assets = assets_from_cache_or_binary(cfg.use_custom_assets, cache_dir)?;
|
||||||
let mut config = cfg.clone();
|
let mut config = cfg.clone();
|
||||||
@@ -206,7 +205,7 @@ pub fn list_themes(
|
|||||||
config.language = Some("Rust");
|
config.language = Some("Rust");
|
||||||
config.style_components = StyleComponents(style);
|
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 buf = String::new();
|
||||||
let mut handle = OutputHandle::FmtWrite(&mut buf);
|
let mut handle = OutputHandle::FmtWrite(&mut buf);
|
||||||
|
|
||||||
@@ -259,7 +258,9 @@ pub fn list_themes(
|
|||||||
fn set_terminal_title_to(new_terminal_title: String) {
|
fn set_terminal_title_to(new_terminal_title: String) {
|
||||||
let osc_command_for_setting_terminal_title = "\x1b]0;";
|
let osc_command_for_setting_terminal_title = "\x1b]0;";
|
||||||
let osc_end_command = "\x07";
|
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();
|
io::stdout().flush().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,7 +427,7 @@ fn run() -> Result<bool> {
|
|||||||
};
|
};
|
||||||
run_controller(inputs, &plain_config, cache_dir)
|
run_controller(inputs, &plain_config, cache_dir)
|
||||||
} else if app.matches.get_flag("list-themes") {
|
} 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)
|
Ok(true)
|
||||||
} else if app.matches.get_flag("config-file") {
|
} else if app.matches.get_flag("config-file") {
|
||||||
println!("{}", config_file().to_string_lossy());
|
println!("{}", config_file().to_string_lossy());
|
||||||
|
|||||||
+8
-8
@@ -216,20 +216,20 @@ impl<'a> Input<'a> {
|
|||||||
description,
|
description,
|
||||||
metadata: self.metadata,
|
metadata: self.metadata,
|
||||||
reader: {
|
reader: {
|
||||||
let mut file = File::open(&path)
|
let path_display =
|
||||||
.map_err(|e| format!("'{}': {e}", path.to_string_lossy()))?;
|
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() {
|
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 {
|
if let Some(stdout) = stdout_identifier {
|
||||||
let input_identifier = Identifier::try_from(file).map_err(|e| {
|
let input_identifier = Identifier::try_from(file)
|
||||||
format!("{}: Error identifying file: {e}", path.to_string_lossy())
|
.map_err(|e| format!("{path_display}: Error identifying file: {e}"))?;
|
||||||
})?;
|
|
||||||
if stdout.surely_conflicts_with(&input_identifier) {
|
if stdout.surely_conflicts_with(&input_identifier) {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"IO circle detected. The input from '{}' is also an output. Aborting to avoid infinite loop.",
|
"IO circle detected. The input from '{path_display}' is also an output. Aborting to avoid infinite loop.",
|
||||||
path.to_string_lossy()
|
|
||||||
)
|
)
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -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",)
|
bat_warning!("$LESSCLOSE exited with nonzero exit code",)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ mod vscreen;
|
|||||||
pub(crate) mod wrapping;
|
pub(crate) mod wrapping;
|
||||||
|
|
||||||
pub use nonprintable_notation::{BinaryBehavior, NonprintableNotation};
|
pub use nonprintable_notation::{BinaryBehavior, NonprintableNotation};
|
||||||
|
pub use preprocessor::sanitize_for_terminal;
|
||||||
pub use preprocessor::StripAnsiMode;
|
pub use preprocessor::StripAnsiMode;
|
||||||
pub use pretty_printer::{Input, PrettyPrinter, Syntax};
|
pub use pretty_printer::{Input, PrettyPrinter, Syntax};
|
||||||
pub use syntax_mapping::{MappingTarget, SyntaxMapping};
|
pub use syntax_mapping::{MappingTarget, SyntaxMapping};
|
||||||
|
|||||||
@@ -149,6 +149,35 @@ pub fn strip_ansi(line: &str) -> String {
|
|||||||
buffer
|
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.
|
/// Strips overstrike sequences (backspace formatting) from input.
|
||||||
///
|
///
|
||||||
/// Overstrike formatting is used by man pages and some help output:
|
/// Overstrike formatting is used by man pages and some help output:
|
||||||
@@ -261,3 +290,40 @@ fn test_strip_overstrike() {
|
|||||||
// Unicode with overstrike
|
// Unicode with overstrike
|
||||||
assert_eq!(strip_overstrike("ä\x08äöü", 2), "äöü");
|
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
@@ -29,7 +29,9 @@ use crate::error::*;
|
|||||||
use crate::input::OpenedInput;
|
use crate::input::OpenedInput;
|
||||||
use crate::line_range::{MaxBufferedLineNumber, RangeCheckResult};
|
use crate::line_range::{MaxBufferedLineNumber, RangeCheckResult};
|
||||||
use crate::output::OutputHandle;
|
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::style::StyleComponent;
|
||||||
use crate::terminal::{as_terminal_escaped, to_ansi_color};
|
use crate::terminal::{as_terminal_escaped, to_ansi_color};
|
||||||
use crate::vscreen::{AnsiStyle, EscapeSequence, EscapeSequenceIterator};
|
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' \
|
(but will be present if the output of 'bat' is piped). You can use 'bat -A' \
|
||||||
to show the binary file contents.",
|
to show the binary file contents.",
|
||||||
Yellow.paint("[bat warning]"),
|
Yellow.paint("[bat warning]"),
|
||||||
input.description.summary(),
|
sanitize_for_terminal(&input.description.summary()),
|
||||||
)?;
|
)?;
|
||||||
} else if self.config.style_components.grid() {
|
} else if self.config.style_components.grid() {
|
||||||
self.print_horizontal_line(handle, '┬')?;
|
self.print_horizontal_line(handle, '┬')?;
|
||||||
@@ -543,9 +545,11 @@ impl Printer for InteractivePrinter<'_> {
|
|||||||
"{}{}{mode}",
|
"{}{}{mode}",
|
||||||
description
|
description
|
||||||
.kind()
|
.kind()
|
||||||
.map(|kind| format!("{kind}: "))
|
.map(|kind| format!("{}: ", sanitize_for_terminal(kind)))
|
||||||
.unwrap_or_else(|| "".into()),
|
.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)
|
self.print_header_multiline_component(handle, &header_filename)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user