nix3-develop: correctly escape ASCII control characters for JSON

Fixes #991.
(cherry picked from commit 138c7161be)

Change-Id: Iafc7d9603fbc3615393d32d9630f0e8fe548950b
This commit is contained in:
Qyriad
2025-11-18 20:38:27 +01:00
parent ced467fe49
commit 53dc27f752
5 changed files with 50 additions and 10 deletions
+9
View File
@@ -0,0 +1,9 @@
---
synopsis: Fix develop shells for derivations with escape codes
issues: [fj#991]
cls: [4154, 4155]
category: Fixes
credits: [Qyriad]
---
ASCII control characters (including `\e`, used for ANSI escape codes) in derivation variables are now correctly escaped for `nix develop` and `nix print-dev-env`, instead of erroring.
+13 -10
View File
@@ -142,20 +142,20 @@ struct BuildEnvironment
for (auto & [name, value] : vars) {
if (!ignoreVars.count(name)) {
if (auto str = std::get_if<String>(&value)) {
out << fmt("%s=%s\n", name, shellEscape(str->value));
out << fmt("%s=%s\n", name, bashEscape(str->value));
if (str->exported)
out << fmt("export %s\n", name);
}
else if (auto arr = std::get_if<Array>(&value)) {
out << "declare -a " << name << "=(";
for (auto & s : *arr)
out << shellEscape(s) << " ";
out << bashEscape(s) << " ";
out << ")\n";
}
else if (auto arr = std::get_if<Associative>(&value)) {
out << "declare -A " << name << "=(";
for (auto & [n, v] : *arr)
out << "[" << shellEscape(n) << "]=" << shellEscape(v) << " ";
out << "[" << bashEscape(n) << "]=" << bashEscape(v) << " ";
out << ")\n";
}
}
@@ -599,21 +599,24 @@ struct CmdDevelop : Common, MixEnvironment
else if (!command.empty()) {
std::vector<std::string> args;
for (auto s : command)
args.push_back(shellEscape(s));
args.push_back(bashEscape(s));
script += fmt("exec %s\n", concatStringsSep(" ", args));
}
else {
script = "[ -n \"$PS1\" ] && [ -e ~/.bashrc ] && source ~/.bashrc;\n" + script;
if (developSettings.bashPrompt != "")
script += fmt("[ -n \"$PS1\" ] && PS1=%s;\n",
shellEscape(developSettings.bashPrompt.get()));
script +=
fmt("[ -n \"$PS1\" ] && PS1=%s;\n",
bashEscape(developSettings.bashPrompt.get()));
if (developSettings.bashPromptPrefix != "")
script += fmt("[ -n \"$PS1\" ] && PS1=%s\"$PS1\";\n",
shellEscape(developSettings.bashPromptPrefix.get()));
script +=
fmt("[ -n \"$PS1\" ] && PS1=%s\"$PS1\";\n",
bashEscape(developSettings.bashPromptPrefix.get()));
if (developSettings.bashPromptSuffix != "")
script += fmt("[ -n \"$PS1\" ] && PS1+=%s;\n",
shellEscape(developSettings.bashPromptSuffix.get()));
script +=
fmt("[ -n \"$PS1\" ] && PS1+=%s;\n",
bashEscape(developSettings.bashPromptSuffix.get()));
}
writeFull(rcFileFd.get(), script);
+26
View File
@@ -12,6 +12,31 @@ fi
__vars="$(declare -p)"
__functions="$(declare -F)"
# Literal control characters (ASCII 0-31) aren't valid JSON.
__escapeCtrl() {
local escaped="$1"
# I don't know if NUL bytes are at ALL possible in here,
# but covering them is free.
local i=0
# NOTE: safe input `i` to arithmetic expansion.
while [[ "$i" -le 32 ]]; do
# Convert the decimal ASCII value to its actual string.
local asHex; printf -v asHex "%02x" "$i"
local asStr; printf -v asStr "%b" "\x$asHex"
# Format it to \uXXXX.
# All control characters fit within four hex digits.
local asUni; printf -v asUni '\\u%04x' "$i"
escaped="${escaped//"$asStr"/"$asUni"}"
i="$((i + 1))"
done
printf "%s" "$escaped"
}
__dumpEnv() {
printf '{\n'
@@ -125,6 +150,7 @@ __escapeString() {
__s="${__s//$'\n'/\\n}"
__s="${__s//$'\r'/\\r}"
__s="${__s//$'\t'/\\t}"
__s="$(__escapeCtrl "$__s")"
printf '"%s"' "$__s"
}
+1
View File
@@ -132,6 +132,7 @@ set -u
[[ ${arr2[1]} = $'\n' ]]
[[ ${arr2[2]} = $'x\ny' ]]
[[ $(fun) = blabla ]]
[[ "$ASCII_ESC" = "$(printf "\e")" ]]
[[ $PATH = $(jq -r .variables.PATH.value $TEST_ROOT/dev-env.json):$path ]]
)
+1
View File
@@ -50,6 +50,7 @@ let pkgs = rec {
VAR_FROM_NIX = "bar";
ASCII_PERCENT = "%";
ASCII_AT = "@";
ASCII_ESC = "";
TEST_inNixShell = if inNixShell then "true" else "false";
inherit stdenv;
outputs = ["dev" "out"];