diff --git a/lib/generators.nix b/lib/generators.nix
index 4317e49c2538f..376aa4081bf4f 100644
--- a/lib/generators.nix
+++ b/lib/generators.nix
@@ -70,6 +70,7 @@ let
split
toJSON
typeOf
+ escapeXML
;
## -- HELPER FUNCTIONS & DEFAULTS --
@@ -548,13 +549,17 @@ in rec {
# Inputs
- Options
- : Empty set, there may be configuration options in the future
+ Structured function argument
+
+ : escape (optional, default: `false`)
+ : If this option is true, XML special characters are escaped in string values and keys
Value
: The value to be converted to Plist
*/
- toPlist = {}: v: let
+ toPlist = {
+ escape ? false
+ }: v: let
expr = ind: x:
if x == null then "" else
if isBool x then bool ind x else
@@ -568,10 +573,12 @@ in rec {
literal = ind: x: ind + x;
+ maybeEscapeXML = if escape then escapeXML else x: x;
+
bool = ind: x: literal ind (if x then "" else "");
int = ind: x: literal ind "${toString x}";
- str = ind: x: literal ind "${x}";
- key = ind: x: literal ind "${x}";
+ str = ind: x: literal ind "${maybeEscapeXML x}";
+ key = ind: x: literal ind "${maybeEscapeXML x}";
float = ind: x: literal ind "${toString x}";
indent = ind: expr "\t${ind}";
@@ -597,7 +604,10 @@ in rec {
(expr "\t${ind}" value)
]) x));
- in ''
+ in
+ # TODO: As discussed in #356502, deprecated functionality should be removed sometime after 25.11.
+ lib.warnIf (!escape && lib.oldestSupportedReleaseIsAtLeast 2505) "Using `lib.generators.toPlist` without `escape = true` is deprecated"
+ ''
${expr "" v}
diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix
index 72b3d630b8da8..2128cf17a3334 100644
--- a/lib/tests/misc.nix
+++ b/lib/tests/misc.nix
@@ -1635,7 +1635,7 @@ runTests {
expected = "«foo»";
};
- testToPlist = {
+ testToPlistUnescaped = {
expr = mapAttrs (const (generators.toPlist { })) {
value = {
nested.values = {
@@ -1651,10 +1651,34 @@ runTests {
emptylist = [];
attrs = { foo = null; "foo b/ar" = "baz"; };
emptyattrs = {};
+ "keys are not " = "and < neither are string values";
};
};
};
- expected = { value = builtins.readFile ./test-to-plist-expected.plist; };
+ expected = { value = builtins.readFile ./test-to-plist-unescaped-expected.plist; };
+ };
+
+ testToPlistEscaped = {
+ expr = mapAttrs (const (generators.toPlist { escape = true; })) {
+ value = {
+ nested.values = {
+ int = 42;
+ float = 0.1337;
+ bool = true;
+ emptystring = "";
+ string = "fn\${o}\"r\\d";
+ newlinestring = "\n";
+ path = /. + "/foo";
+ null_ = null;
+ list = [ 3 4 "test" ];
+ emptylist = [];
+ attrs = { foo = null; "foo b/ar" = "baz"; };
+ emptyattrs = {};
+ "keys are " = "and < so are string values";
+ };
+ };
+ };
+ expected = { value = builtins.readFile ./test-to-plist-escaped-expected.plist; };
};
testToLuaEmptyAttrSet = {
diff --git a/lib/tests/test-to-plist-escaped-expected.plist b/lib/tests/test-to-plist-escaped-expected.plist
new file mode 100644
index 0000000000000..6ccf05aecc43b
--- /dev/null
+++ b/lib/tests/test-to-plist-escaped-expected.plist
@@ -0,0 +1,48 @@
+
+
+
+
+ nested
+
+ values
+
+ attrs
+
+ foo b/ar
+ baz
+
+ bool
+
+ emptyattrs
+
+
+
+ emptylist
+
+
+
+ emptystring
+
+ float
+ 0.133700
+ int
+ 42
+ keys are <escaped>
+ and < so are string values
+ list
+
+ 3
+ 4
+ test
+
+ newlinestring
+
+
+ path
+ /foo
+ string
+ fn${o}"r\d
+
+
+
+
\ No newline at end of file
diff --git a/lib/tests/test-to-plist-expected.plist b/lib/tests/test-to-plist-unescaped-expected.plist
similarity index 90%
rename from lib/tests/test-to-plist-expected.plist
rename to lib/tests/test-to-plist-unescaped-expected.plist
index df0528a60767b..ea8d95699f731 100644
--- a/lib/tests/test-to-plist-expected.plist
+++ b/lib/tests/test-to-plist-unescaped-expected.plist
@@ -27,6 +27,8 @@
0.133700
int
42
+ keys are not
+ and < neither are string values
list
3