1package grpcurl
2
3import (
4	"bufio"
5	"bytes"
6	"encoding/base64"
7	"encoding/json"
8	"fmt"
9	"io"
10	"reflect"
11	"strings"
12	"sync"
13
14	"github.com/golang/protobuf/jsonpb" //lint:ignore SA1019 we have to import this because it appears in exported API
15	"github.com/golang/protobuf/proto"  //lint:ignore SA1019 we have to import this because it appears in exported API
16	"github.com/jhump/protoreflect/desc"
17	"github.com/jhump/protoreflect/dynamic"
18	"google.golang.org/grpc/codes"
19	"google.golang.org/grpc/metadata"
20	"google.golang.org/grpc/status"
21)
22
23// RequestParser processes input into messages.
24type RequestParser interface {
25	// Next parses input data into the given request message. If called after
26	// input is exhausted, it returns io.EOF. If the caller re-uses the same
27	// instance in multiple calls to Next, it should call msg.Reset() in between
28	// each call.
29	Next(msg proto.Message) error
30	// NumRequests returns the number of messages that have been parsed and
31	// returned by a call to Next.
32	NumRequests() int
33}
34
35type jsonRequestParser struct {
36	dec          *json.Decoder
37	unmarshaler  jsonpb.Unmarshaler
38	requestCount int
39}
40
41// NewJSONRequestParser returns a RequestParser that reads data in JSON format
42// from the given reader. The given resolver is used to assist with decoding of
43// google.protobuf.Any messages.
44//
45// Input data that contains more than one message should just include all
46// messages concatenated (though whitespace is necessary to separate some kinds
47// of values in JSON).
48//
49// If the given reader has no data, the returned parser will return io.EOF on
50// the very first call.
51func NewJSONRequestParser(in io.Reader, resolver jsonpb.AnyResolver) RequestParser {
52	return &jsonRequestParser{
53		dec:         json.NewDecoder(in),
54		unmarshaler: jsonpb.Unmarshaler{AnyResolver: resolver},
55	}
56}
57
58// NewJSONRequestParserWithUnmarshaler is like NewJSONRequestParser but
59// accepts a protobuf jsonpb.Unmarshaler instead of jsonpb.AnyResolver.
60func NewJSONRequestParserWithUnmarshaler(in io.Reader, unmarshaler jsonpb.Unmarshaler) RequestParser {
61	return &jsonRequestParser{
62		dec:         json.NewDecoder(in),
63		unmarshaler: unmarshaler,
64	}
65}
66
67func (f *jsonRequestParser) Next(m proto.Message) error {
68	var msg json.RawMessage
69	if err := f.dec.Decode(&msg); err != nil {
70		return err
71	}
72	f.requestCount++
73	return f.unmarshaler.Unmarshal(bytes.NewReader(msg), m)
74}
75
76func (f *jsonRequestParser) NumRequests() int {
77	return f.requestCount
78}
79
80const (
81	textSeparatorChar = '\x1e'
82)
83
84type textRequestParser struct {
85	r            *bufio.Reader
86	err          error
87	requestCount int
88}
89
90// NewTextRequestParser returns a RequestParser that reads data in the protobuf
91// text format from the given reader.
92//
93// Input data that contains more than one message should include an ASCII
94// 'Record Separator' character (0x1E) between each message.
95//
96// Empty text is a valid text format and represents an empty message. So if the
97// given reader has no data, the returned parser will yield an empty message
98// for the first call to Next and then return io.EOF thereafter. This also means
99// that if the input data ends with a record separator, then a final empty
100// message will be parsed *after* the separator.
101func NewTextRequestParser(in io.Reader) RequestParser {
102	return &textRequestParser{r: bufio.NewReader(in)}
103}
104
105func (f *textRequestParser) Next(m proto.Message) error {
106	if f.err != nil {
107		return f.err
108	}
109
110	var b []byte
111	b, f.err = f.r.ReadBytes(textSeparatorChar)
112	if f.err != nil && f.err != io.EOF {
113		return f.err
114	}
115	// remove delimiter
116	if len(b) > 0 && b[len(b)-1] == textSeparatorChar {
117		b = b[:len(b)-1]
118	}
119
120	f.requestCount++
121
122	return proto.UnmarshalText(string(b), m)
123}
124
125func (f *textRequestParser) NumRequests() int {
126	return f.requestCount
127}
128
129// Formatter translates messages into string representations.
130type Formatter func(proto.Message) (string, error)
131
132// NewJSONFormatter returns a formatter that returns JSON strings. The JSON will
133// include empty/default values (instead of just omitted them) if emitDefaults
134// is true. The given resolver is used to assist with encoding of
135// google.protobuf.Any messages.
136func NewJSONFormatter(emitDefaults bool, resolver jsonpb.AnyResolver) Formatter {
137	marshaler := jsonpb.Marshaler{
138		EmitDefaults: emitDefaults,
139		Indent:       "  ",
140		AnyResolver:  resolver,
141	}
142	return marshaler.MarshalToString
143}
144
145// NewTextFormatter returns a formatter that returns strings in the protobuf
146// text format. If includeSeparator is true then, when invoked to format
147// multiple messages, all messages after the first one will be prefixed with the
148// ASCII 'Record Separator' character (0x1E).
149func NewTextFormatter(includeSeparator bool) Formatter {
150	tf := textFormatter{useSeparator: includeSeparator}
151	return tf.format
152}
153
154type textFormatter struct {
155	useSeparator bool
156	numFormatted int
157}
158
159var protoTextMarshaler = proto.TextMarshaler{ExpandAny: true}
160
161func (tf *textFormatter) format(m proto.Message) (string, error) {
162	var buf bytes.Buffer
163	if tf.useSeparator && tf.numFormatted > 0 {
164		if err := buf.WriteByte(textSeparatorChar); err != nil {
165			return "", err
166		}
167	}
168
169	// If message implements MarshalText method (such as a *dynamic.Message),
170	// it won't get details about whether or not to format to text compactly
171	// or with indentation. So first see if the message also implements a
172	// MarshalTextIndent method and use that instead if available.
173	type indentMarshaler interface {
174		MarshalTextIndent() ([]byte, error)
175	}
176
177	if indenter, ok := m.(indentMarshaler); ok {
178		b, err := indenter.MarshalTextIndent()
179		if err != nil {
180			return "", err
181		}
182		if _, err := buf.Write(b); err != nil {
183			return "", err
184		}
185	} else if err := protoTextMarshaler.Marshal(&buf, m); err != nil {
186		return "", err
187	}
188
189	// no trailing newline needed
190	str := buf.String()
191	if len(str) > 0 && str[len(str)-1] == '\n' {
192		str = str[:len(str)-1]
193	}
194
195	tf.numFormatted++
196
197	return str, nil
198}
199
200type Format string
201
202const (
203	FormatJSON = Format("json")
204	FormatText = Format("text")
205)
206
207// AnyResolverFromDescriptorSource returns an AnyResolver that will search for
208// types using the given descriptor source.
209func AnyResolverFromDescriptorSource(source DescriptorSource) jsonpb.AnyResolver {
210	return &anyResolver{source: source}
211}
212
213// AnyResolverFromDescriptorSourceWithFallback returns an AnyResolver that will
214// search for types using the given descriptor source and then fallback to a
215// special message if the type is not found. The fallback type will render to
216// JSON with a "@type" property, just like an Any message, but also with a
217// custom "@value" property that includes the binary encoded payload.
218func AnyResolverFromDescriptorSourceWithFallback(source DescriptorSource) jsonpb.AnyResolver {
219	res := anyResolver{source: source}
220	return &anyResolverWithFallback{AnyResolver: &res}
221}
222
223type anyResolver struct {
224	source DescriptorSource
225
226	er dynamic.ExtensionRegistry
227
228	mu       sync.RWMutex
229	mf       *dynamic.MessageFactory
230	resolved map[string]func() proto.Message
231}
232
233func (r *anyResolver) Resolve(typeUrl string) (proto.Message, error) {
234	mname := typeUrl
235	if slash := strings.LastIndex(mname, "/"); slash >= 0 {
236		mname = mname[slash+1:]
237	}
238
239	r.mu.RLock()
240	factory := r.resolved[mname]
241	r.mu.RUnlock()
242
243	// already resolved?
244	if factory != nil {
245		return factory(), nil
246	}
247
248	r.mu.Lock()
249	defer r.mu.Unlock()
250
251	// double-check, in case we were racing with another goroutine
252	// that resolved this one
253	factory = r.resolved[mname]
254	if factory != nil {
255		return factory(), nil
256	}
257
258	// use descriptor source to resolve message type
259	d, err := r.source.FindSymbol(mname)
260	if err != nil {
261		return nil, err
262	}
263	md, ok := d.(*desc.MessageDescriptor)
264	if !ok {
265		return nil, fmt.Errorf("unknown message: %s", typeUrl)
266	}
267	// populate any extensions for this message, too
268	if exts, err := r.source.AllExtensionsForType(mname); err != nil {
269		return nil, err
270	} else if err := r.er.AddExtension(exts...); err != nil {
271		return nil, err
272	}
273
274	if r.mf == nil {
275		r.mf = dynamic.NewMessageFactoryWithExtensionRegistry(&r.er)
276	}
277
278	factory = func() proto.Message {
279		return r.mf.NewMessage(md)
280	}
281	if r.resolved == nil {
282		r.resolved = map[string]func() proto.Message{}
283	}
284	r.resolved[mname] = factory
285	return factory(), nil
286}
287
288// anyResolverWithFallback can provide a fallback value for unknown
289// messages that will format itself to JSON using an "@value" field
290// that has the base64-encoded data for the unknown message value.
291type anyResolverWithFallback struct {
292	jsonpb.AnyResolver
293}
294
295func (r anyResolverWithFallback) Resolve(typeUrl string) (proto.Message, error) {
296	msg, err := r.AnyResolver.Resolve(typeUrl)
297	if err == nil {
298		return msg, err
299	}
300
301	// Try "default" resolution logic. This mirrors the default behavior
302	// of jsonpb, which checks to see if the given message name is registered
303	// in the proto package.
304	mname := typeUrl
305	if slash := strings.LastIndex(mname, "/"); slash >= 0 {
306		mname = mname[slash+1:]
307	}
308	//lint:ignore SA1019 new non-deprecated API requires other code changes; deferring...
309	mt := proto.MessageType(mname)
310	if mt != nil {
311		return reflect.New(mt.Elem()).Interface().(proto.Message), nil
312	}
313
314	// finally, fallback to a special placeholder that can marshal itself
315	// to JSON using a special "@value" property to show base64-encoded
316	// data for the embedded message
317	return &unknownAny{TypeUrl: typeUrl, Error: fmt.Sprintf("%s is not recognized; see @value for raw binary message data", mname)}, nil
318}
319
320type unknownAny struct {
321	TypeUrl string `json:"@type"`
322	Error   string `json:"@error"`
323	Value   string `json:"@value"`
324}
325
326func (a *unknownAny) MarshalJSONPB(jsm *jsonpb.Marshaler) ([]byte, error) {
327	if jsm.Indent != "" {
328		return json.MarshalIndent(a, "", jsm.Indent)
329	}
330	return json.Marshal(a)
331}
332
333func (a *unknownAny) Unmarshal(b []byte) error {
334	a.Value = base64.StdEncoding.EncodeToString(b)
335	return nil
336}
337
338func (a *unknownAny) Reset() {
339	a.Value = ""
340}
341
342func (a *unknownAny) String() string {
343	b, err := a.MarshalJSONPB(&jsonpb.Marshaler{})
344	if err != nil {
345		return fmt.Sprintf("ERROR: %v", err.Error())
346	}
347	return string(b)
348}
349
350func (a *unknownAny) ProtoMessage() {
351}
352
353var _ proto.Message = (*unknownAny)(nil)
354
355// FormatOptions is a set of flags that are passed to a JSON or text formatter.
356type FormatOptions struct {
357	// EmitJSONDefaultFields flag, when true, includes empty/default values in the output.
358	// FormatJSON only flag.
359	EmitJSONDefaultFields bool
360
361	// AllowUnknownFields is an option for the parser. When true,
362	// it accepts input which includes unknown fields. These unknown fields
363	// are skipped instead of returning an error.
364	// FormatJSON only flag.
365	AllowUnknownFields bool
366
367	// IncludeTextSeparator is true then, when invoked to format multiple messages,
368	// all messages after the first one will be prefixed with the
369	// ASCII 'Record Separator' character (0x1E).
370	// It might be useful when the output is piped to another grpcurl process.
371	// FormatText only flag.
372	IncludeTextSeparator bool
373}
374
375// RequestParserAndFormatter returns a request parser and formatter for the
376// given format. The given descriptor source may be used for parsing message
377// data (if needed by the format).
378// It accepts a set of options. The field EmitJSONDefaultFields and IncludeTextSeparator
379// are options for JSON and protobuf text formats, respectively. The AllowUnknownFields field
380// is a JSON-only format flag.
381// Requests will be parsed from the given in.
382func RequestParserAndFormatter(format Format, descSource DescriptorSource, in io.Reader, opts FormatOptions) (RequestParser, Formatter, error) {
383	switch format {
384	case FormatJSON:
385		resolver := AnyResolverFromDescriptorSource(descSource)
386		unmarshaler := jsonpb.Unmarshaler{AnyResolver: resolver, AllowUnknownFields: opts.AllowUnknownFields}
387		return NewJSONRequestParserWithUnmarshaler(in, unmarshaler), NewJSONFormatter(opts.EmitJSONDefaultFields, anyResolverWithFallback{AnyResolver: resolver}), nil
388	case FormatText:
389		return NewTextRequestParser(in), NewTextFormatter(opts.IncludeTextSeparator), nil
390	default:
391		return nil, nil, fmt.Errorf("unknown format: %s", format)
392	}
393}
394
395// RequestParserAndFormatterFor returns a request parser and formatter for the
396// given format. The given descriptor source may be used for parsing message
397// data (if needed by the format). The flags emitJSONDefaultFields and
398// includeTextSeparator are options for JSON and protobuf text formats,
399// respectively. Requests will be parsed from the given in.
400// This function is deprecated. Please use RequestParserAndFormatter instead.
401// DEPRECATED
402func RequestParserAndFormatterFor(format Format, descSource DescriptorSource, emitJSONDefaultFields, includeTextSeparator bool, in io.Reader) (RequestParser, Formatter, error) {
403	return RequestParserAndFormatter(format, descSource, in, FormatOptions{
404		EmitJSONDefaultFields: emitJSONDefaultFields,
405		IncludeTextSeparator:  includeTextSeparator,
406	})
407}
408
409// DefaultEventHandler logs events to a writer. This is not thread-safe, but is
410// safe for use with InvokeRPC as long as NumResponses and Status are not read
411// until the call to InvokeRPC completes.
412type DefaultEventHandler struct {
413	Out       io.Writer
414	Formatter Formatter
415	// 0 = default
416	// 1 = verbose
417	// 2 = very verbose
418	VerbosityLevel int
419
420	// NumResponses is the number of responses that have been received.
421	NumResponses int
422	// Status is the status that was received at the end of an RPC. It is
423	// nil if the RPC is still in progress.
424	Status *status.Status
425}
426
427// NewDefaultEventHandler returns an InvocationEventHandler that logs events to
428// the given output. If verbose is true, all events are logged. Otherwise, only
429// response messages are logged.
430//
431// Deprecated: NewDefaultEventHandler exists for compatability.
432// It doesn't allow fine control over the `VerbosityLevel`
433// and provides only 0 and 1 options (which corresponds to the `verbose` argument).
434// Use DefaultEventHandler{} initializer directly.
435func NewDefaultEventHandler(out io.Writer, descSource DescriptorSource, formatter Formatter, verbose bool) *DefaultEventHandler {
436	verbosityLevel := 0
437	if verbose {
438		verbosityLevel = 1
439	}
440	return &DefaultEventHandler{
441		Out:            out,
442		Formatter:      formatter,
443		VerbosityLevel: verbosityLevel,
444	}
445}
446
447var _ InvocationEventHandler = (*DefaultEventHandler)(nil)
448
449func (h *DefaultEventHandler) OnResolveMethod(md *desc.MethodDescriptor) {
450	if h.VerbosityLevel > 0 {
451		txt, err := GetDescriptorText(md, nil)
452		if err == nil {
453			fmt.Fprintf(h.Out, "\nResolved method descriptor:\n%s\n", txt)
454		}
455	}
456}
457
458func (h *DefaultEventHandler) OnSendHeaders(md metadata.MD) {
459	if h.VerbosityLevel > 0 {
460		fmt.Fprintf(h.Out, "\nRequest metadata to send:\n%s\n", MetadataToString(md))
461	}
462}
463
464func (h *DefaultEventHandler) OnReceiveHeaders(md metadata.MD) {
465	if h.VerbosityLevel > 0 {
466		fmt.Fprintf(h.Out, "\nResponse headers received:\n%s\n", MetadataToString(md))
467	}
468}
469
470func (h *DefaultEventHandler) OnReceiveResponse(resp proto.Message) {
471	h.NumResponses++
472	if h.VerbosityLevel > 1 {
473		fmt.Fprintf(h.Out, "\nEstimated response size: %d bytes\n", proto.Size(resp))
474	}
475	if h.VerbosityLevel > 0 {
476		fmt.Fprint(h.Out, "\nResponse contents:\n")
477	}
478	if respStr, err := h.Formatter(resp); err != nil {
479		fmt.Fprintf(h.Out, "Failed to format response message %d: %v\n", h.NumResponses, err)
480	} else {
481		fmt.Fprintln(h.Out, respStr)
482	}
483}
484
485func (h *DefaultEventHandler) OnReceiveTrailers(stat *status.Status, md metadata.MD) {
486	h.Status = stat
487	if h.VerbosityLevel > 0 {
488		fmt.Fprintf(h.Out, "\nResponse trailers received:\n%s\n", MetadataToString(md))
489	}
490}
491
492// PrintStatus prints details about the given status to the given writer. The given
493// formatter is used to print any detail messages that may be included in the status.
494// If the given status has a code of OK, "OK" is printed and that is all. Otherwise,
495// "ERROR:" is printed along with a line showing the code, one showing the message
496// string, and each detail message if any are present. The detail messages will be
497// printed as proto text format or JSON, depending on the given formatter.
498func PrintStatus(w io.Writer, stat *status.Status, formatter Formatter) {
499	if stat.Code() == codes.OK {
500		fmt.Fprintln(w, "OK")
501		return
502	}
503	fmt.Fprintf(w, "ERROR:\n  Code: %s\n  Message: %s\n", stat.Code().String(), stat.Message())
504
505	statpb := stat.Proto()
506	if len(statpb.Details) > 0 {
507		fmt.Fprintf(w, "  Details:\n")
508		for i, det := range statpb.Details {
509			prefix := fmt.Sprintf("  %d)", i+1)
510			fmt.Fprintf(w, "%s\t", prefix)
511			prefix = strings.Repeat(" ", len(prefix)) + "\t"
512
513			output, err := formatter(det)
514			if err != nil {
515				fmt.Fprintf(w, "Error parsing detail message: %v\n", err)
516			} else {
517				lines := strings.Split(output, "\n")
518				for i, line := range lines {
519					if i == 0 {
520						// first line is already indented
521						fmt.Fprintf(w, "%s\n", line)
522					} else {
523						fmt.Fprintf(w, "%s%s\n", prefix, line)
524					}
525				}
526			}
527		}
528	}
529}
530