feat: add 'ipfs multibase' commands (#8180)
* add multibase helper commands (encode, decode, list) Co-authored-by: Marcin Rataj <lidel@lidel.org>
Cory Schwartz committed
Aug 18, 2021 at 13:48 UTC
7db27bf83a10cd9804a4be8e7421b5ed52bda461
4 files changed
+228
-13
core/commands/commands_test.go
+17
-13
@@ -90,20 +90,26 @@ func TestCommands(t *testing.T) {
90
"/bootstrap/rm",
91
"/bootstrap/rm/all",
92
"/cat",
93
+ "/cid",
94
+ "/cid/base32",
95
+ "/cid/bases",
96
+ "/cid/codecs",
97
+ "/cid/format",
98
+ "/cid/hashes",
99
"/commands",
100
"/commands/completion",
101
"/commands/completion/bash",
102
"/config",
103
"/config/edit",
98
- "/config/replace",
99
- "/config/show",
104
"/config/profile",
105
"/config/profile/apply",
106
+ "/config/replace",
107
+ "/config/show",
108
"/dag",
103
- "/dag/get",
109
"/dag/export",
105
- "/dag/put",
110
+ "/dag/get",
111
"/dag/import",
112
+ "/dag/put",
113
"/dag/resolve",
114
"/dag/stat",
115
"/dht",
@@ -132,16 +138,16 @@ func TestCommands(t *testing.T) {
138
"/files/read",
139
"/files/rm",
140
"/files/stat",
141
+ "/files/write",
142
"/filestore",
143
"/filestore/dups",
144
"/filestore/ls",
145
"/filestore/verify",
139
- "/files/write",
146
"/get",
147
"/id",
148
"/key",
143
- "/key/gen",
149
"/key/export",
150
+ "/key/gen",
151
"/key/import",
152
"/key/list",
153
"/key/rename",
@@ -153,12 +159,16 @@ func TestCommands(t *testing.T) {
159
"/log/tail",
160
"/ls",
161
"/mount",
162
+ "/multibase",
163
+ "/multibase/decode",
164
+ "/multibase/encode",
165
+ "/multibase/list",
166
"/name",
167
"/name/publish",
168
"/name/pubsub",
169
+ "/name/pubsub/cancel",
170
"/name/pubsub/state",
171
"/name/pubsub/subs",
161
- "/name/pubsub/cancel",
172
"/name/resolve",
173
"/object",
174
"/object/data",
@@ -235,12 +245,6 @@ func TestCommands(t *testing.T) {
245
"/urlstore/add",
246
"/version",
247
"/version/deps",
238
- "/cid",
239
- "/cid/format",
240
- "/cid/base32",
241
- "/cid/codecs",
242
- "/cid/bases",
243
- "/cid/hashes",
248
}
249
250
cmdSet := make(map[string]struct{})
core/commands/multibase.go
new
+118
@@ -0,0 +1,118 @@
1
+package commands
2
+
3
+import (
4
+ "bytes"
5
+ "fmt"
6
+ "io/ioutil"
7
+ "strings"
8
+
9
+ cmds "github.com/ipfs/go-ipfs-cmds"
10
+ "github.com/ipfs/go-ipfs/core/commands/cmdenv"
11
+ mbase "github.com/multiformats/go-multibase"
12
+)
13
+
14
+var MbaseCmd = &cmds.Command{
15
+ Helptext: cmds.HelpText{
16
+ Tagline: "Encode and decode files or stdin with multibase format",
17
+ },
18
+ Subcommands: map[string]*cmds.Command{
19
+ "encode": mbaseEncodeCmd,
20
+ "decode": mbaseDecodeCmd,
21
+ "list": basesCmd,
22
+ },
23
+ Extra: CreateCmdExtras(SetDoesNotUseRepo(true)),
24
+}
25
+
26
+const (
27
+ mbaseOptionName = "b"
28
+)
29
+
30
+var mbaseEncodeCmd = &cmds.Command{
31
+ Helptext: cmds.HelpText{
32
+ Tagline: "Encode data into multibase string",
33
+ LongDescription: `
34
+This command expects a file name or data provided via stdin.
35
+
36
+By default it will use URL-safe base64url encoding,
37
+but one can customize used base with -b:
38
+
39
+ > echo hello | ipfs multibase encode -b base16 > output_file
40
+ > cat output_file
41
+ f68656c6c6f0a
42
+
43
+ > echo hello > input_file
44
+ > ipfs multibase encode -b base16 input_file
45
+ f68656c6c6f0a
46
+ `,
47
+ },
48
+ Arguments: []cmds.Argument{
49
+ cmds.FileArg("file", true, false, "data to encode").EnableStdin(),
50
+ },
51
+ Options: []cmds.Option{
52
+ cmds.StringOption(mbaseOptionName, "multibase encoding").WithDefault("base64url"),
53
+ },
54
+ Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) error {
55
+ if err := req.ParseBodyArgs(); err != nil {
56
+ return err
57
+ }
58
+ encoderName, _ := req.Options[mbaseOptionName].(string)
59
+ encoder, err := mbase.EncoderByName(encoderName)
60
+ if err != nil {
61
+ return err
62
+ }
63
+ files := req.Files.Entries()
64
+ file, err := cmdenv.GetFileArg(files)
65
+ if err != nil {
66
+ return fmt.Errorf("failed to access file: %w", err)
67
+ }
68
+ buf, err := ioutil.ReadAll(file)
69
+ if err != nil {
70
+ return fmt.Errorf("failed to read file contents: %w", err)
71
+ }
72
+ encoded := encoder.Encode(buf)
73
+ reader := strings.NewReader(encoded)
74
+ return resp.Emit(reader)
75
+ },
76
+}
77
+
78
+var mbaseDecodeCmd = &cmds.Command{
79
+ Helptext: cmds.HelpText{
80
+ Tagline: "Decode multibase string",
81
+ LongDescription: `
82
+This command expects multibase inside of a file or via stdin:
83
+
84
+ > echo -n hello | ipfs multibase encode -b base16 > file
85
+ > cat file
86
+ f68656c6c6f
87
+
88
+ > ipfs multibase decode file
89
+ hello
90
+
91
+ > cat file | ipfs multibase decode
92
+ hello
93
+`,
94
+ },
95
+ Arguments: []cmds.Argument{
96
+ cmds.FileArg("encoded_file", true, false, "encoded data to decode").EnableStdin(),
97
+ },
98
+ Run: func(req *cmds.Request, resp cmds.ResponseEmitter, env cmds.Environment) error {
99
+ if err := req.ParseBodyArgs(); err != nil {
100
+ return err
101
+ }
102
+ files := req.Files.Entries()
103
+ file, err := cmdenv.GetFileArg(files)
104
+ if err != nil {
105
+ return fmt.Errorf("failed to access file: %w", err)
106
+ }
107
+ encoded_data, err := ioutil.ReadAll(file)
108
+ if err != nil {
109
+ return fmt.Errorf("failed to read file contents: %w", err)
110
+ }
111
+ _, data, err := mbase.Decode(string(encoded_data))
112
+ if err != nil {
113
+ return fmt.Errorf("failed to decode multibase: %w", err)
114
+ }
115
+ reader := bytes.NewReader(data)
116
+ return resp.Emit(reader)
117
+ },
118
+}
core/commands/root.go
+4
@@ -43,7 +43,10 @@ DATA STRUCTURE COMMANDS
43
dag Interact with IPLD DAG nodes
44
files Interact with files as if they were a unix filesystem
45
block Interact with raw blocks in the datastore
46
+
47
+TEXT ENCODING COMMANDS
48
cid Convert and discover properties of CIDs
49
+ multibase Encode and decode data with Multibase format
50
51
ADVANCED COMMANDS
52
daemon Start a long-running daemon process
@@ -151,6 +154,7 @@ var rootSubcommands = map[string]*cmds.Command{
154
"version": VersionCmd,
155
"shutdown": daemonShutdownCmd,
156
"cid": CidCmd,
157
+ "multibase": MbaseCmd,
158
}
159
160
// RootRO is the readonly version of Root
test/sharness/t0295-multibase.sh
new
+89
@@ -0,0 +1,89 @@
1
+#!/usr/bin/env bash
2
+
3
+test_description="Test multibase commands"
4
+
5
+. lib/test-lib.sh
6
+
7
+# note: all "ipfs multibase" commands should work without requiring a repo
8
+
9
+cat <<EOF > bases_expect
10
+ 0 identity
11
+0 48 base2
12
+b 98 base32
13
+B 66 base32upper
14
+c 99 base32pad
15
+C 67 base32padupper
16
+f 102 base16
17
+F 70 base16upper
18
+k 107 base36
19
+K 75 base36upper
20
+m 109 base64
21
+M 77 base64pad
22
+t 116 base32hexpad
23
+T 84 base32hexpadupper
24
+u 117 base64url
25
+U 85 base64urlpad
26
+v 118 base32hex
27
+V 86 base32hexupper
28
+z 122 base58btc
29
+Z 90 base58flickr
30
+EOF
31
+
32
+# TODO: expose same cmd under multibase?
33
+test_expect_success "multibase list" '
34
+ cut -c 10- bases_expect > expect &&
35
+ ipfs multibase list > actual &&
36
+ test_cmp expect actual
37
+'
38
+
39
+test_expect_success "multibase encode works (stdin)" '
40
+ echo -n uaGVsbG8 > expected &&
41
+ echo -n hello | ipfs multibase encode > actual &&
42
+ test_cmp actual expected
43
+'
44
+
45
+test_expect_success "multibase encode works (file)" '
46
+ echo -n hello > file &&
47
+ echo -n uaGVsbG8 > expected &&
48
+ ipfs multibase encode ./file > actual &&
49
+ test_cmp actual expected
50
+'
51
+
52
+test_expect_success "multibase encode -b (custom base)" '
53
+ echo -n f68656c6c6f > expected &&
54
+ echo -n hello | ipfs multibase encode -b base16 > actual &&
55
+ test_cmp actual expected
56
+'
57
+
58
+test_expect_success "multibase decode works (stdin)" '
59
+ echo -n hello > expected &&
60
+ echo -n uaGVsbG8 | ipfs multibase decode > actual &&
61
+ test_cmp actual expected
62
+'
63
+
64
+test_expect_success "multibase decode works (file)" '
65
+ echo -n uaGVsbG8 > file &&
66
+ echo -n hello > expected &&
67
+ ipfs multibase decode ./file > actual &&
68
+ test_cmp actual expected
69
+'
70
+
71
+test_expect_success "multibase encode+decode roundtrip" '
72
+ echo -n hello > expected &&
73
+ cat expected | ipfs multibase encode -b base64 | ipfs multibase decode > actual &&
74
+ test_cmp actual expected
75
+'
76
+
77
+test_expect_success "multibase error on unknown multibase prefix" '
78
+ echo "Error: failed to decode multibase: selected encoding not supported" > expected &&
79
+ echo -n ę-that-should-do-the-trick | ipfs multibase decode 2> actual ;
80
+ test_cmp actual expected
81
+'
82
+
83
+test_expect_success "multibase error on a character outside of the base" "
84
+ echo \"Error: failed to decode multibase: encoding/hex: invalid byte: U+007A 'z'\" > expected &&
85
+ echo -n f6c6f6cz | ipfs multibase decode 2> actual ;
86
+ test_cmp actual expected
87
+"
88
+
89
+test_done