Skip to content

Commit fb0a9ac

Browse files
authored
Merge pull request #7831 from ipfs/fix/escape-nonprintable-chars
Escape non-printable characters in user output
2 parents 0d63fbb + 20132a8 commit fb0a9ac

File tree

13 files changed

+131
-45
lines changed

13 files changed

+131
-45
lines changed

core/commands/add.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ only-hash, and progress/status related flags) will change the final hash.
353353
if quiet {
354354
fmt.Fprintf(os.Stdout, "%s\n", output.Hash)
355355
} else {
356-
fmt.Fprintf(os.Stdout, "added %s %s\n", output.Hash, output.Name)
356+
fmt.Fprintf(os.Stdout, "added %s %s\n", output.Hash, cmdenv.EscNonPrint(output.Name))
357357
}
358358

359359
} else {

core/commands/cmdenv/env.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cmdenv
22

33
import (
44
"fmt"
5+
"strconv"
56
"strings"
67

78
"github.com/ipfs/go-ipfs/commands"
@@ -70,3 +71,29 @@ func GetConfigRoot(env cmds.Environment) (string, error) {
7071

7172
return ctx.ConfigRoot, nil
7273
}
74+
75+
// EscNonPrint converts non-printable characters and backslash into Go escape
76+
// sequences. This is done to display all characters in a string, including
77+
// those that would otherwise not be displayed or have an undesirable effect on
78+
// the display.
79+
func EscNonPrint(s string) string {
80+
if !needEscape(s) {
81+
return s
82+
}
83+
84+
esc := strconv.Quote(s)
85+
// Remove first and last quote, and unescape quotes.
86+
return strings.ReplaceAll(esc[1:len(esc)-1], `\"`, `"`)
87+
}
88+
89+
func needEscape(s string) bool {
90+
if strings.ContainsRune(s, '\\') {
91+
return true
92+
}
93+
for _, r := range s {
94+
if !strconv.IsPrint(r) {
95+
return true
96+
}
97+
}
98+
return false
99+
}

core/commands/cmdenv/env_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package cmdenv
2+
3+
import (
4+
"strconv"
5+
"testing"
6+
)
7+
8+
func TestEscNonPrint(t *testing.T) {
9+
b := []byte("hello")
10+
b[2] = 0x7f
11+
s := string(b)
12+
if !needEscape(s) {
13+
t.Fatal("string needs escaping")
14+
}
15+
if !hasNonPrintable(s) {
16+
t.Fatal("expected non-printable")
17+
}
18+
if hasNonPrintable(EscNonPrint(s)) {
19+
t.Fatal("escaped string has non-printable")
20+
}
21+
if EscNonPrint(`hel\lo`) != `hel\\lo` {
22+
t.Fatal("backslash not escaped")
23+
}
24+
25+
s = `hello`
26+
if needEscape(s) {
27+
t.Fatal("string does not need escaping")
28+
}
29+
if EscNonPrint(s) != s {
30+
t.Fatal("string should not have changed")
31+
}
32+
s = `"hello"`
33+
if EscNonPrint(s) != s {
34+
t.Fatal("string should not have changed")
35+
}
36+
if EscNonPrint(`"hel\"lo"`) != `"hel\\"lo"` {
37+
t.Fatal("did not get expected escaped string")
38+
}
39+
}
40+
41+
func hasNonPrintable(s string) bool {
42+
for _, r := range s {
43+
if !strconv.IsPrint(r) {
44+
return true
45+
}
46+
}
47+
return false
48+
}

core/commands/dns.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"io"
66

7+
cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv"
78
ncmd "github.com/ipfs/go-ipfs/core/commands/name"
89
namesys "github.com/ipfs/go-ipfs/namesys"
910
nsopts "github.com/ipfs/interface-go-ipfs-core/options/namesys"
@@ -77,7 +78,7 @@ The resolver can recursively resolve:
7778
},
7879
Encoders: cmds.EncoderMap{
7980
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *ncmd.ResolvedPath) error {
80-
fmt.Fprintln(w, out.Path.String())
81+
fmt.Fprintln(w, cmdenv.EscNonPrint(out.Path.String()))
8182
return nil
8283
}),
8384
},

core/commands/keystore.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -383,9 +383,9 @@ var keyRenameCmd = &cmds.Command{
383383
Encoders: cmds.EncoderMap{
384384
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, kro *KeyRenameOutput) error {
385385
if kro.Overwrite {
386-
fmt.Fprintf(w, "Key %s renamed to %s with overwriting\n", kro.Id, kro.Now)
386+
fmt.Fprintf(w, "Key %s renamed to %s with overwriting\n", kro.Id, cmdenv.EscNonPrint(kro.Now))
387387
} else {
388-
fmt.Fprintf(w, "Key %s renamed to %s\n", kro.Id, kro.Now)
388+
fmt.Fprintf(w, "Key %s renamed to %s\n", kro.Id, cmdenv.EscNonPrint(kro.Now))
389389
}
390390
return nil
391391
}),
@@ -547,9 +547,9 @@ func keyOutputListEncoders() cmds.EncoderFunc {
547547
tw := tabwriter.NewWriter(w, 1, 2, 1, ' ', 0)
548548
for _, s := range list.Keys {
549549
if withID {
550-
fmt.Fprintf(tw, "%s\t%s\t\n", s.Id, s.Name)
550+
fmt.Fprintf(tw, "%s\t%s\t\n", s.Id, cmdenv.EscNonPrint(s.Name))
551551
} else {
552-
fmt.Fprintf(tw, "%s\n", s.Name)
552+
fmt.Fprintf(tw, "%s\n", cmdenv.EscNonPrint(s.Name))
553553
}
554554
}
555555
tw.Flush()

core/commands/ls.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ func tabularOutput(req *cmds.Request, w io.Writer, out *LsOutput, lastObjectHash
251251
}
252252
}
253253

254-
fmt.Fprintf(tw, s, link.Hash, link.Size, link.Name)
254+
fmt.Fprintf(tw, s, link.Hash, link.Size, cmdenv.EscNonPrint(link.Name))
255255
}
256256
}
257257
tw.Flush()

core/commands/mount_unix.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,8 @@ baz
119119
Type: config.Mounts{},
120120
Encoders: cmds.EncoderMap{
121121
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, mounts *config.Mounts) error {
122-
fmt.Fprintf(w, "IPFS mounted at: %s\n", mounts.IPFS)
123-
fmt.Fprintf(w, "IPNS mounted at: %s\n", mounts.IPNS)
122+
fmt.Fprintf(w, "IPFS mounted at: %s\n", cmdenv.EscNonPrint(mounts.IPFS))
123+
fmt.Fprintf(w, "IPNS mounted at: %s\n", cmdenv.EscNonPrint(mounts.IPNS))
124124

125125
return nil
126126
}),

core/commands/name/publish.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,9 @@ Alternatively, publish an <ipfs-path> using a valid PeerID (as listed by
152152
var err error
153153
quieter, _ := req.Options[quieterOptionName].(bool)
154154
if quieter {
155-
_, err = fmt.Fprintln(w, ie.Name)
155+
_, err = fmt.Fprintln(w, cmdenv.EscNonPrint(ie.Name))
156156
} else {
157-
_, err = fmt.Fprintf(w, "Published to %s: %s\n", ie.Name, ie.Value)
157+
_, err = fmt.Fprintf(w, "Published to %s: %s\n", cmdenv.EscNonPrint(ie.Name), cmdenv.EscNonPrint(ie.Value))
158158
}
159159
return err
160160
}),

core/commands/object/object.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ multihash.
167167
fmt.Fprintln(tw, "Hash\tSize\tName")
168168
}
169169
for _, link := range out.Links {
170-
fmt.Fprintf(tw, "%s\t%v\t%s\n", link.Hash, link.Size, link.Name)
170+
fmt.Fprintf(tw, "%s\t%v\t%s\n", link.Hash, link.Size, cmdenv.EscNonPrint(link.Name))
171171
}
172172
tw.Flush()
173173

core/commands/pin/remotepin.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ Returns a list of objects that are pinned to a remote pinning service.
254254
Encoders: cmds.EncoderMap{
255255
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *RemotePinOutput) error {
256256
// pin remote ls produces a flat output similar to legacy pin ls
257-
fmt.Fprintf(w, "%s\t%s\t%s\n", out.Cid, out.Status, out.Name)
257+
fmt.Fprintf(w, "%s\t%s\t%s\n", out.Cid, out.Status, cmdenv.EscNonPrint(out.Name))
258258
return nil
259259
}),
260260
},

0 commit comments

Comments
 (0)