File 0021-govis-support-double-quote-escapes.patch of Package go-mtree
From bd7b72e03780e1f32a6971a23017f4c3920739d0 Mon Sep 17 00:00:00 2001
From: Aleksa Sarai <cyphar@cyphar.com>
Date: Tue, 23 Sep 2025 17:27:24 +1000
Subject: [PATCH 21/25] govis: support double quote escapes
This is supported by both OpenBSD and FreBSD so it seems possible that
we will run into \" sequences at some point. The handling is basically
identical to \\ sequences.
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
---
pkg/govis/govis.go | 21 +++++++++++----------
pkg/govis/unvis.go | 4 ++--
pkg/govis/unvis_test.go | 13 +++++++------
pkg/govis/vis.go | 3 ++-
pkg/govis/vis_test.go | 5 +++++
5 files changed, 27 insertions(+), 19 deletions(-)
diff --git a/pkg/govis/govis.go b/pkg/govis/govis.go
index 4200af2418ce..cf78a08c55b0 100644
--- a/pkg/govis/govis.go
+++ b/pkg/govis/govis.go
@@ -32,16 +32,17 @@ type VisFlag uint
// mtree only uses one set of flags, implementing them all is necessary in
// order to have compatibility with BSD's vis() and unvis() commands.
const (
- VisOctal VisFlag = (1 << iota) // VIS_OCTAL: Use octal \ddd format.
- VisCStyle // VIS_CSTYLE: Use \[nrft0..] where appropriate.
- VisSpace // VIS_SP: Also encode space.
- VisTab // VIS_TAB: Also encode tab.
- VisNewline // VIS_NL: Also encode newline.
- VisSafe // VIS_SAFE: Encode unsafe characters.
- VisNoSlash // VIS_NOSLASH: Inhibit printing '\'.
- VisHTTPStyle // VIS_HTTPSTYLE: HTTP-style escape %xx.
- VisGlob // VIS_GLOB: Encode glob(3) magics.
- visMask VisFlag = (1 << iota) - 1 // Mask of all flags.
+ VisOctal VisFlag = (1 << iota) // VIS_OCTAL: Use octal \ddd format.
+ VisCStyle // VIS_CSTYLE: Use \[nrft0..] where appropriate.
+ VisSpace // VIS_SP: Also encode space.
+ VisTab // VIS_TAB: Also encode tab.
+ VisNewline // VIS_NL: Also encode newline.
+ VisSafe // VIS_SAFE: Encode unsafe characters.
+ VisNoSlash // VIS_NOSLASH: Inhibit printing '\'.
+ VisHTTPStyle // VIS_HTTPSTYLE: HTTP-style escape %xx.
+ VisGlob // VIS_GLOB: Encode glob(3) magics.
+ VisDoubleQuote // VIS_DQ: Encode double-quotes (").
+ visMask VisFlag = (1 << iota) - 1 // Mask of all flags.
VisWhite VisFlag = (VisSpace | VisTab | VisNewline)
)
diff --git a/pkg/govis/unvis.go b/pkg/govis/unvis.go
index 7f20ad7a1955..b90fe72da9d1 100644
--- a/pkg/govis/unvis.go
+++ b/pkg/govis/unvis.go
@@ -225,9 +225,9 @@ func (p *unvisParser) escapeSequence() error {
}
switch ch {
- case '\\':
+ case '\\', '"':
p.Step()
- return p.output.WriteByte('\\')
+ return p.output.WriteByte(byte(ch))
case '0', '1', '2', '3', '4', '5', '6', '7':
return p.escapeDigits(8, false)
diff --git a/pkg/govis/unvis_test.go b/pkg/govis/unvis_test.go
index ae9945f52cd5..5915ce3984ec 100644
--- a/pkg/govis/unvis_test.go
+++ b/pkg/govis/unvis_test.go
@@ -65,12 +65,13 @@ func TestUnvisCStyleEscape(t *testing.T) {
expected string
}{
{"", ""},
- {"\\n\\v\\t\\s", "\n\v\t "},
- {"\\\\n\\tt", "\\n\tt"},
- {"\\b", "\b"},
- {"\\r\\b\\n", "\r\b\n"},
- {"\\a\\a\\b", "\x07\x07\b"},
- {"\\f\\s\\E", "\f \x1b"},
+ {`\n\v\t\s`, "\n\v\t "},
+ {`\\n\tt`, "\\n\tt"},
+ {`\b`, "\b"},
+ {`\r\b\n`, "\r\b\n"},
+ {`\a\a\b`, "\x07\x07\b"},
+ {`\f\s\E`, "\f \x1b"},
+ {`\"foo\"\\"bar`, `"foo"\"bar`},
// Hidden markers. They actually aren't generated by vis(3) but for
// some reason, they're supported...
{"test\\\ning", "testing"},
diff --git a/pkg/govis/vis.go b/pkg/govis/vis.go
index e7efc6b32e5b..2965bcd0267c 100644
--- a/pkg/govis/vis.go
+++ b/pkg/govis/vis.go
@@ -81,7 +81,8 @@ func vis(output *strings.Builder, ch byte, flag VisFlag) {
// We must *always* encode stuff characters not in ASCII.
case flag&VisGlob == VisGlob && isglob(ch):
// Glob characters are graphical but can be forced to be encoded.
- case flag&VisNoSlash == 0 && ch == '\\':
+ case flag&VisNoSlash == 0 && ch == '\\',
+ flag&VisDoubleQuote == VisDoubleQuote && ch == '"':
// Prefix \ if applicable.
_ = output.WriteByte('\\')
fallthrough
diff --git a/pkg/govis/vis_test.go b/pkg/govis/vis_test.go
index f7b7a32488cb..327a9f898f5f 100644
--- a/pkg/govis/vis_test.go
+++ b/pkg/govis/vis_test.go
@@ -104,6 +104,11 @@ func TestVisFlags(t *testing.T) {
{"62_\u00c6\u00c62\u00ae\u00b7m\u00db\u00c3r^\u00bfp\u00c6u'q\u00fbc2\u00f0u\u00b8\u00dd\u00e8v\u00ff\u00b0\u00dc\u00c2\u00f53\u00db-k\u00f2sd4\\p\u00da\u00a6\u00d3\u00eea<\u00e6s{\u00a0p\u00f0\u00ffj\u00e0\u00e8\u00b8\u00b8\u00bc\u00fcb", `62_\303\206\303\2062\302\256\302\267m\303\233\303\203r^\302\277p\303\206u'q\303\273c2\303\260u\302\270\303\235\303\250v\303\277\302\260\303\234\303\202\303\2653\303\233-k\303\262sd4\\p\303\232\302\246\303\223\303\256a<\303\246s{\302\240p\303\260\303\277j\303\240\303\250\302\270\302\270\302\274\303\274b`, VisGlob | VisOctal},
{"'3Ze\u050e|\u02del\u069du-Rpct4+Z5b={@_{b", `'3Ze\M-T\M^N|\M-K\M^^l\M-Z\M^]u-Rpct4+Z5b={@_{b`, VisGlob},
{"'3Ze\u050e|\u02del\u069du-Rpct4+Z5b={@_{b", `'3Ze\324\216|\313\236l\332\235u-Rpct4+Z5b={@_{b`, VisGlob | VisOctal},
+ // VisDoubleQuote
+ {`Foo="Bar's"`, `Foo="Bar's"`, 0},
+ {`Foo="Bar's"`, `Foo=\"Bar's\"`, VisDoubleQuote},
+ {`"“Unicode” ‘Quote’s’"`, `\"\M-b\M^@\M^\Unicode\M-b\M^@\M^] \M-b\M^@\M^XQuote\M-b\M^@\M^Ys\M-b\M^@\M^Y\"`, VisDoubleQuote},
+ {`"“Unicode” ‘Quote’s’"`, `\"\342\200\234Unicode\342\200\235 \342\200\230Quote\342\200\231s\342\200\231\"`, VisDoubleQuote | VisOctal},
} {
t.Run(fmt.Sprintf("Test%.2d", idx), func(t *testing.T) {
enc, err := Vis(test.input, test.flag)
--
2.51.0