Skip to content

Commit a589406

Browse files
committed
ls: encode path when using --hyperlink
1 parent 7333573 commit a589406

File tree

4 files changed

+48
-2
lines changed

4 files changed

+48
-2
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/uu/ls/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ uucore = { workspace = true, features = [
3232
once_cell = { workspace = true }
3333
selinux = { workspace = true, optional = true }
3434
hostname = { workspace = true }
35+
regex = { workspace = true }
3536

3637
[[bin]]
3738
name = "ls"

src/uu/ls/src/ls.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use glob::{MatchOptions, Pattern};
1313
use lscolors::{LsColors, Style};
1414

1515
use number_prefix::NumberPrefix;
16+
use regex::{Captures, Regex};
1617
use std::{cell::OnceCell, num::IntErrorKind};
1718
use std::{collections::HashSet, io::IsTerminal};
1819

@@ -3017,9 +3018,20 @@ fn display_file_name(
30173018
let absolute_path = fs::canonicalize(&path.p_buf).unwrap_or_default();
30183019
let absolute_path = absolute_path.to_string_lossy();
30193020

3020-
// TODO encode path
3021+
let re = Regex::new(r"[^A-Za-z0-9_\-\.:/]").unwrap();
3022+
// percentage encoding of path
3023+
let absolute_path = re.replace_all(&absolute_path, |caps: &Captures| -> String {
3024+
format!("%{:02x}", caps[0].as_bytes().first().unwrap())
3025+
});
3026+
3027+
let filename = if std::io::stdout().is_terminal() {
3028+
name
3029+
} else {
3030+
name.trim_matches('\'').to_string()
3031+
};
3032+
30213033
// \x1b = ESC, \x07 = BEL
3022-
name = format!("\x1b]8;;file://{hostname}{absolute_path}\x07{name}\x1b]8;;\x07");
3034+
name = format!("\x1b]8;;file://{hostname}{absolute_path}\x07{filename}\x1b]8;;\x07");
30233035
}
30243036

30253037
if let Some(ls_colors) = &config.color {

tests/by-util/test_ls.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3890,6 +3890,38 @@ fn test_ls_hyperlink() {
38903890
.stdout_is(format!("{file}\n"));
38913891
}
38923892

3893+
// spell-checker: disable
3894+
#[test]
3895+
fn test_ls_hyperlink_encode_link() {
3896+
let (at, mut ucmd) = at_and_ucmd!();
3897+
3898+
#[cfg(not(target_os = "windows"))]
3899+
{
3900+
at.touch("back\\slash");
3901+
at.touch("ques?tion");
3902+
}
3903+
at.touch("encoded%3Fquestion");
3904+
at.touch("sp ace");
3905+
3906+
let result = ucmd.arg("--hyperlink").succeeds();
3907+
#[cfg(not(target_os = "windows"))]
3908+
{
3909+
assert!(result
3910+
.stdout_str()
3911+
.contains("back%5cslash\x07back\\slash\x1b]8;;\x07"));
3912+
assert!(result
3913+
.stdout_str()
3914+
.contains("ques%3ftion\x07ques?tion\x1b]8;;\x07"));
3915+
}
3916+
assert!(result
3917+
.stdout_str()
3918+
.contains("encoded%253Fquestion\x07encoded%3Fquestion\x1b]8;;\x07"));
3919+
assert!(result
3920+
.stdout_str()
3921+
.contains("sp%20ace\x07sp ace\x1b]8;;\x07"));
3922+
}
3923+
// spell-checker: enable
3924+
38933925
#[test]
38943926
fn test_ls_color_do_not_reset() {
38953927
let scene: TestScenario = TestScenario::new(util_name!());

0 commit comments

Comments
 (0)