2
3
import (
4
"context"
5
- "encoding/binary"
5
"fmt"
6
"io"
7
+ "io/ioutil"
8
"net/http"
9
"sort"
10
11
cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv"
12
+ mbase "github.com/multiformats/go-multibase"
13
+ "github.com/pkg/errors"
14
15
cmds "github.com/ipfs/go-ipfs-cmds"
16
options "github.com/ipfs/interface-go-ipfs-core/options"
23
ipfs pubsub allows you to publish messages to a given topic, and also to
24
subscribe to new messages on a given topic.
25
24
-This is an experimental feature. It is not intended in its current state
25
-to be used in a production environment.
26
+EXPERIMENTAL FEATURE
27
27
-To use, the daemon must be run with '--enable-pubsub-experiment'.
28
+ It is not intended in its current state to be used in a production
29
+ environment. To use, the daemon must be run with
30
+ '--enable-pubsub-experiment'.
31
`,
32
},
33
Subcommands: map[string]*cmds.Command{
38
},
39
}
40
38
-const (
39
- pubsubDiscoverOptionName = "discover"
40
-)
41
-
41
type pubsubMessage struct {
43
- From []byte `json:"from,omitempty"`
44
- Data []byte `json:"data,omitempty"`
45
- Seqno []byte `json:"seqno,omitempty"`
42
+ From string `json:"from,omitempty"`
43
+ Data string `json:"data,omitempty"`
44
+ Seqno string `json:"seqno,omitempty"`
45
TopicIDs []string `json:"topicIDs,omitempty"`
46
}
47
51
ShortDescription: `
52
ipfs pubsub sub subscribes to messages on a given topic.
53
55
-This is an experimental feature. It is not intended in its current state
56
-to be used in a production environment.
54
+EXPERIMENTAL FEATURE
55
58
-To use, the daemon must be run with '--enable-pubsub-experiment'.
59
-`,
60
- LongDescription: `
61
-ipfs pubsub sub subscribes to messages on a given topic.
56
+ It is not intended in its current state to be used in a production
57
+ environment. To use, the daemon must be run with
58
+ '--enable-pubsub-experiment'.
59
63
-This is an experimental feature. It is not intended in its current state
64
-to be used in a production environment.
60
+PEER ENCODING
61
66
-To use, the daemon must be run with '--enable-pubsub-experiment'.
62
+ Peer IDs in From fields are encoded using the default text representation
63
+ from go-libp2p. This ensures the same string values as in 'ipfs pubsub peers'.
64
68
-This command outputs data in the following encodings:
69
- * "json"
70
-(Specified by the "--encoding" or "--enc" flag)
65
+TOPIC AND DATA ENCODING
66
+
67
+ Topics, Data and Seqno are binary data. To ensure all bytes are transferred
68
+ correctly the RPC client and server will use multibase encoding behind
69
+ the scenes.
70
+
71
+ You can inspect the format by passing --enc=json. The ipfs multibase commands
72
+ can be used for encoding/decoding multibase strings in the userland.
73
`,
74
},
75
Arguments: []cmds.Argument{
74
- cmds.StringArg("topic", true, false, "String name of topic to subscribe to."),
75
- },
76
- Options: []cmds.Option{
77
- cmds.BoolOption(pubsubDiscoverOptionName, "Deprecated option to instruct pubsub to discovery peers for the topic. Discovery is now built into pubsub."),
76
+ cmds.StringArg("topic", true, false, "Name of topic to subscribe to."),
77
},
78
+ PreRun: urlArgsEncoder,
79
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
80
api, err := cmdenv.GetApi(env, req)
81
if err != nil {
82
return err
83
}
84
+ if err := urlArgsDecoder(req, env); err != nil {
85
+ return err
86
+ }
87
88
topic := req.Arguments[0]
89
+
90
sub, err := api.PubSub().Subscribe(req.Context, topic)
91
if err != nil {
92
return err
105
return err
106
}
107
104
- if err := res.Emit(&pubsubMessage{
105
- Data: msg.Data(),
106
- From: []byte(msg.From()),
107
- Seqno: msg.Seq(),
108
- TopicIDs: msg.Topics(),
109
- }); err != nil {
108
+ // turn bytes into strings
109
+ encoder, _ := mbase.EncoderByName("base64url")
110
+ psm := pubsubMessage{
111
+ Data: encoder.Encode(msg.Data()),
112
+ From: msg.From().Pretty(),
113
+ Seqno: encoder.Encode(msg.Seq()),
114
+ }
115
+ for _, topic := range msg.Topics() {
116
+ psm.TopicIDs = append(psm.TopicIDs, encoder.Encode([]byte(topic)))
117
+ }
118
+ if err := res.Emit(&psm); err != nil {
119
return err
120
}
121
}
122
},
123
Encoders: cmds.EncoderMap{
124
cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, psm *pubsubMessage) error {
116
- _, err := w.Write(psm.Data)
125
+ _, dec, err := mbase.Decode(psm.Data)
126
+ if err != nil {
127
+ return err
128
+ }
129
+ _, err = w.Write(dec)
130
return err
131
}),
132
+ // DEPRECATED, undocumented format we used in tests, but not anymore
133
+ // <message.payload>\n<message.payload>\n
134
"ndpayload": cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, psm *pubsubMessage) error {
120
- psm.Data = append(psm.Data, '\n')
121
- _, err := w.Write(psm.Data)
122
- return err
135
+ return errors.New("--enc=ndpayload was removed, use --enc=json instead")
136
}),
137
+ // DEPRECATED, uncodumented format we used in tests, but not anymore
138
+ // <varint-len><message.payload><varint-len><message.payload>
139
"lenpayload": cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, psm *pubsubMessage) error {
125
- buf := make([]byte, 8, len(psm.Data)+8)
126
-
127
- n := binary.PutUvarint(buf, uint64(len(psm.Data)))
128
- buf = append(buf[:n], psm.Data...)
129
- _, err := w.Write(buf)
130
- return err
140
+ return errors.New("--enc=lenpayload was removed, use --enc=json instead")
141
}),
142
},
143
Type: pubsubMessage{},
145
146
var PubsubPubCmd = &cmds.Command{
147
Helptext: cmds.HelpText{
138
- Tagline: "Publish a message to a given pubsub topic.",
148
+ Tagline: "Publish data to a given pubsub topic.",
149
ShortDescription: `
150
ipfs pubsub pub publishes a message to a specified topic.
151
+It reads binary data from stdin or a file.
152
+
153
+EXPERIMENTAL FEATURE
154
+
155
+ It is not intended in its current state to be used in a production
156
+ environment. To use, the daemon must be run with
157
+ '--enable-pubsub-experiment'.
158
142
-This is an experimental feature. It is not intended in its current state
143
-to be used in a production environment.
159
+HTTP RPC ENCODING
160
+
161
+ The data to be published is sent in HTTP request body as multipart/form-data.
162
+
163
+ Topic names are binary data too. To ensure all bytes are transferred
164
+ correctly via URL params, the RPC client and server will use multibase
165
+ encoding behind the scenes.
166
145
-To use, the daemon must be run with '--enable-pubsub-experiment'.
167
`,
168
},
169
Arguments: []cmds.Argument{
170
cmds.StringArg("topic", true, false, "Topic to publish to."),
150
- cmds.StringArg("data", true, true, "Payload of message to publish.").EnableStdin(),
171
+ cmds.FileArg("data", true, false, "The data to be published.").EnableStdin(),
172
},
173
+ PreRun: urlArgsEncoder,
174
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
175
api, err := cmdenv.GetApi(env, req)
176
if err != nil {
177
return err
178
}
179
+ if err := urlArgsDecoder(req, env); err != nil {
180
+ return err
181
+ }
182
183
topic := req.Arguments[0]
184
160
- err = req.ParseBodyArgs()
185
+ // read data passed as a file
186
+ file, err := cmdenv.GetFileArg(req.Files.Entries())
187
if err != nil {
188
return err
189
}
164
-
165
- for _, data := range req.Arguments[1:] {
166
- if err := api.PubSub().Publish(req.Context, topic, []byte(data)); err != nil {
167
- return err
168
- }
190
+ defer file.Close()
191
+ data, err := ioutil.ReadAll(file)
192
+ if err != nil {
193
+ return err
194
}
195
171
- return nil
196
+ // publish
197
+ return api.PubSub().Publish(req.Context, topic, data)
198
},
199
}
200
204
ShortDescription: `
205
ipfs pubsub ls lists out the names of topics you are currently subscribed to.
206
181
-This is an experimental feature. It is not intended in its current state
182
-to be used in a production environment.
207
+EXPERIMENTAL FEATURE
208
+
209
+ It is not intended in its current state to be used in a production
210
+ environment. To use, the daemon must be run with
211
+ '--enable-pubsub-experiment'.
212
184
-To use, the daemon must be run with '--enable-pubsub-experiment'.
213
+TOPIC ENCODING
214
+
215
+ Topic names are a binary data. To ensure all bytes are transferred
216
+ correctly RPC client and server will use multibase encoding behind
217
+ the scenes.
218
+
219
+ You can inspect the format by passing --enc=json. ipfs multibase commands
220
+ can be used for encoding/decoding multibase strings in the userland.
221
`,
222
},
223
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
231
return err
232
}
233
234
+ // emit topics encoded in multibase
235
+ encoder, _ := mbase.EncoderByName("base64url")
236
+ for n, topic := range l {
237
+ l[n] = encoder.Encode([]byte(topic))
238
+ }
239
+
240
return cmds.EmitOnce(res, stringList{l})
241
},
242
Type: stringList{},
243
Encoders: cmds.EncoderMap{
202
- cmds.Text: cmds.MakeTypedEncoder(stringListEncoder),
244
+ cmds.Text: cmds.MakeTypedEncoder(multibaseDecodedStringListEncoder),
245
},
246
}
247
206
-func stringListEncoder(req *cmds.Request, w io.Writer, list *stringList) error {
248
+func multibaseDecodedStringListEncoder(req *cmds.Request, w io.Writer, list *stringList) error {
249
+ for n, mb := range list.Strings {
250
+ _, data, err := mbase.Decode(mb)
251
+ if err != nil {
252
+ return err
253
+ }
254
+ list.Strings[n] = string(data)
255
+ }
256
+ return safeTextListEncoder(req, w, list)
257
+}
258
+
259
+// converts list of strings to text representation where each string is placed
260
+// in separate line with non-printable/unsafe characters escaped
261
+// (this protects terminal output from being mangled by non-ascii topic names)
262
+func safeTextListEncoder(req *cmds.Request, w io.Writer, list *stringList) error {
263
for _, str := range list.Strings {
264
_, err := fmt.Fprintf(w, "%s\n", cmdenv.EscNonPrint(str))
265
if err != nil {
274
Tagline: "List peers we are currently pubsubbing with.",
275
ShortDescription: `
276
ipfs pubsub peers with no arguments lists out the pubsub peers you are
221
-currently connected to. If given a topic, it will list connected
222
-peers who are subscribed to the named topic.
277
+currently connected to. If given a topic, it will list connected peers who are
278
+subscribed to the named topic.
279
+
280
+EXPERIMENTAL FEATURE
281
224
-This is an experimental feature. It is not intended in its current state
225
-to be used in a production environment.
282
+ It is not intended in its current state to be used in a production
283
+ environment. To use, the daemon must be run with
284
+ '--enable-pubsub-experiment'.
285
227
-To use, the daemon must be run with '--enable-pubsub-experiment'.
286
+TOPIC AND DATA ENCODING
287
+
288
+ Topic names are a binary data. To ensure all bytes are transferred
289
+ correctly RPC client and server will use multibase encoding behind
290
+ the scenes.
291
+
292
+ You can inspect the format by passing --enc=json. ipfs multibase commands
293
+ can be used for encoding/decoding multibase strings in the userland.
294
`,
295
},
296
Arguments: []cmds.Argument{
231
- cmds.StringArg("topic", false, false, "topic to list connected peers of"),
297
+ cmds.StringArg("topic", false, false, "Topic to list connected peers of."),
298
},
299
+ PreRun: urlArgsEncoder,
300
Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
301
api, err := cmdenv.GetApi(env, req)
302
if err != nil {
303
return err
304
}
305
+ if err := urlArgsDecoder(req, env); err != nil {
306
+ return err
307
+ }
308
309
var topic string
310
if len(req.Arguments) == 1 {
326
},
327
Type: stringList{},
328
Encoders: cmds.EncoderMap{
259
- cmds.Text: cmds.MakeTypedEncoder(stringListEncoder),
329
+ cmds.Text: cmds.MakeTypedEncoder(safeTextListEncoder),
330
},
331
}
332
+
333
+// TODO: move to cmdenv?
334
+// Encode binary data to be passed as multibase string in URL arguments.
335
+// (avoiding issues described in https://github.com/ipfs/go-ipfs/issues/7939)
336
+func urlArgsEncoder(req *cmds.Request, env cmds.Environment) error {
337
+ encoder, _ := mbase.EncoderByName("base64url")
338
+ for n, arg := range req.Arguments {
339
+ req.Arguments[n] = encoder.Encode([]byte(arg))
340
+ }
341
+ return nil
342
+}
343
+
344
+// Decode binary data passed as multibase string in URL arguments.
345
+// (avoiding issues described in https://github.com/ipfs/go-ipfs/issues/7939)
346
+func urlArgsDecoder(req *cmds.Request, env cmds.Environment) error {
347
+ for n, arg := range req.Arguments {
348
+ encoding, data, err := mbase.Decode(arg)
349
+ if err != nil {
350
+ return errors.Wrap(err, "URL arg must be multibase encoded")
351
+ }
352
+
353
+ // Enforce URL-safe encoding is used for data passed via URL arguments
354
+ // - without this we get data corruption similar to https://github.com/ipfs/go-ipfs/issues/7939
355
+ // - we can't just deny base64, because there may be other bases that
356
+ // are not URL-safe – better to force base64url which is known to be
357
+ // safe in URL context
358
+ if encoding != mbase.Base64url {
359
+ return errors.New("URL arg must be base64url encoded")
360
+ }
361
+
362
+ req.Arguments[n] = string(data)
363
+ }
364
+ return nil
365
+}