1// Copyright 2020 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package protorange_test 6 7import ( 8 "fmt" 9 "strings" 10 "time" 11 12 "google.golang.org/protobuf/encoding/protojson" 13 "google.golang.org/protobuf/internal/detrand" 14 "google.golang.org/protobuf/proto" 15 "google.golang.org/protobuf/reflect/protopath" 16 "google.golang.org/protobuf/reflect/protorange" 17 "google.golang.org/protobuf/reflect/protoreflect" 18 "google.golang.org/protobuf/testing/protopack" 19 "google.golang.org/protobuf/types/known/anypb" 20 "google.golang.org/protobuf/types/known/timestamppb" 21 22 newspb "google.golang.org/protobuf/internal/testprotos/news" 23) 24 25func init() { 26 detrand.Disable() 27} 28 29func mustMarshal(m proto.Message) []byte { 30 b, err := proto.Marshal(m) 31 if err != nil { 32 panic(err) 33 } 34 return b 35} 36 37// Range through every message and clear the unknown fields. 38func Example_discardUnknown() { 39 // Populate the article with unknown fields. 40 m := &newspb.Article{} 41 m.ProtoReflect().SetUnknown(protopack.Message{ 42 protopack.Tag{1000, protopack.BytesType}, protopack.String("Hello, world!"), 43 }.Marshal()) 44 fmt.Println("has unknown fields?", len(m.ProtoReflect().GetUnknown()) > 0) 45 46 // Range through the message and clear all unknown fields. 47 fmt.Println("clear unknown fields") 48 protorange.Range(m.ProtoReflect(), func(proto protopath.Values) error { 49 m, ok := proto.Index(-1).Value.Interface().(protoreflect.Message) 50 if ok && len(m.GetUnknown()) > 0 { 51 m.SetUnknown(nil) 52 } 53 return nil 54 }) 55 fmt.Println("has unknown fields?", len(m.ProtoReflect().GetUnknown()) > 0) 56 57 // Output: 58 // has unknown fields? true 59 // clear unknown fields 60 // has unknown fields? false 61} 62 63// Print the relative paths as Range iterates through a message 64// in a depth-first order. 65func Example_printPaths() { 66 m := &newspb.Article{ 67 Author: "Russ Cox", 68 Date: timestamppb.New(time.Date(2019, time.November, 8, 0, 0, 0, 0, time.UTC)), 69 Title: "Go Turns 10", 70 Content: "Happy birthday, Go! This weekend we celebrate the 10th anniversary of the Go release...", 71 Status: newspb.Article_PUBLISHED, 72 Tags: []string{"community", "birthday"}, 73 Attachments: []*anypb.Any{{ 74 TypeUrl: "google.golang.org.BinaryAttachment", 75 Value: mustMarshal(&newspb.BinaryAttachment{ 76 Name: "gopher-birthday.png", 77 Data: []byte("<binary data>"), 78 }), 79 }}, 80 } 81 82 // Traverse over all reachable values and print the path. 83 protorange.Range(m.ProtoReflect(), func(p protopath.Values) error { 84 fmt.Println(p.Path[1:]) 85 return nil 86 }) 87 88 // Output: 89 // .author 90 // .date 91 // .date.seconds 92 // .title 93 // .content 94 // .status 95 // .tags 96 // .tags[0] 97 // .tags[1] 98 // .attachments 99 // .attachments[0] 100 // .attachments[0].(google.golang.org.BinaryAttachment) 101 // .attachments[0].(google.golang.org.BinaryAttachment).name 102 // .attachments[0].(google.golang.org.BinaryAttachment).data 103} 104 105// Implement a basic text formatter by ranging through all populated values 106// in a message in depth-first order. 107func Example_formatText() { 108 m := &newspb.Article{ 109 Author: "Brad Fitzpatrick", 110 Date: timestamppb.New(time.Date(2018, time.February, 16, 0, 0, 0, 0, time.UTC)), 111 Title: "Go 1.10 is released", 112 Content: "Happy Friday, happy weekend! Today the Go team is happy to announce the release of Go 1.10...", 113 Status: newspb.Article_PUBLISHED, 114 Tags: []string{"go1.10", "release"}, 115 Attachments: []*anypb.Any{{ 116 TypeUrl: "google.golang.org.KeyValueAttachment", 117 Value: mustMarshal(&newspb.KeyValueAttachment{ 118 Name: "checksums.txt", 119 Data: map[string]string{ 120 "go1.10.src.tar.gz": "07cbb9d0091b846c6aea40bf5bc0cea7", 121 "go1.10.darwin-amd64.pkg": "cbb38bb6ff6ea86279e01745984445bf", 122 "go1.10.linux-amd64.tar.gz": "6b3d0e4a5c77352cf4275573817f7566", 123 "go1.10.windows-amd64.msi": "57bda02030f58f5d2bf71943e1390123", 124 }, 125 }), 126 }}, 127 } 128 129 // Print a message in a humanly readable format. 130 var indent []byte 131 protorange.Options{ 132 Stable: true, 133 }.Range(m.ProtoReflect(), 134 func(p protopath.Values) error { 135 // Print the key. 136 var fd protoreflect.FieldDescriptor 137 last := p.Index(-1) 138 beforeLast := p.Index(-2) 139 switch last.Step.Kind() { 140 case protopath.FieldAccessStep: 141 fd = last.Step.FieldDescriptor() 142 fmt.Printf("%s%s: ", indent, fd.Name()) 143 case protopath.ListIndexStep: 144 fd = beforeLast.Step.FieldDescriptor() // lists always appear in the context of a repeated field 145 fmt.Printf("%s%d: ", indent, last.Step.ListIndex()) 146 case protopath.MapIndexStep: 147 fd = beforeLast.Step.FieldDescriptor() // maps always appear in the context of a repeated field 148 fmt.Printf("%s%v: ", indent, last.Step.MapIndex().Interface()) 149 case protopath.AnyExpandStep: 150 fmt.Printf("%s[%v]: ", indent, last.Value.Message().Descriptor().FullName()) 151 case protopath.UnknownAccessStep: 152 fmt.Printf("%s?: ", indent) 153 } 154 155 // Starting printing the value. 156 switch v := last.Value.Interface().(type) { 157 case protoreflect.Message: 158 fmt.Printf("{\n") 159 indent = append(indent, '\t') 160 case protoreflect.List: 161 fmt.Printf("[\n") 162 indent = append(indent, '\t') 163 case protoreflect.Map: 164 fmt.Printf("{\n") 165 indent = append(indent, '\t') 166 case protoreflect.EnumNumber: 167 var ev protoreflect.EnumValueDescriptor 168 if fd != nil { 169 ev = fd.Enum().Values().ByNumber(v) 170 } 171 if ev != nil { 172 fmt.Printf("%v\n", ev.Name()) 173 } else { 174 fmt.Printf("%v\n", v) 175 } 176 case string, []byte: 177 fmt.Printf("%q\n", v) 178 default: 179 fmt.Printf("%v\n", v) 180 } 181 return nil 182 }, 183 func(p protopath.Values) error { 184 // Finish printing the value. 185 last := p.Index(-1) 186 switch last.Value.Interface().(type) { 187 case protoreflect.Message: 188 indent = indent[:len(indent)-1] 189 fmt.Printf("%s}\n", indent) 190 case protoreflect.List: 191 indent = indent[:len(indent)-1] 192 fmt.Printf("%s]\n", indent) 193 case protoreflect.Map: 194 indent = indent[:len(indent)-1] 195 fmt.Printf("%s}\n", indent) 196 } 197 return nil 198 }, 199 ) 200 201 // Output: 202 // { 203 // author: "Brad Fitzpatrick" 204 // date: { 205 // seconds: 1518739200 206 // } 207 // title: "Go 1.10 is released" 208 // content: "Happy Friday, happy weekend! Today the Go team is happy to announce the release of Go 1.10..." 209 // attachments: [ 210 // 0: { 211 // [google.golang.org.KeyValueAttachment]: { 212 // name: "checksums.txt" 213 // data: { 214 // go1.10.darwin-amd64.pkg: "cbb38bb6ff6ea86279e01745984445bf" 215 // go1.10.linux-amd64.tar.gz: "6b3d0e4a5c77352cf4275573817f7566" 216 // go1.10.src.tar.gz: "07cbb9d0091b846c6aea40bf5bc0cea7" 217 // go1.10.windows-amd64.msi: "57bda02030f58f5d2bf71943e1390123" 218 // } 219 // } 220 // } 221 // ] 222 // tags: [ 223 // 0: "go1.10" 224 // 1: "release" 225 // ] 226 // status: PUBLISHED 227 // } 228} 229 230// Scan all protobuf string values for a sensitive word and replace it with 231// a suitable alternative. 232func Example_sanitizeStrings() { 233 m := &newspb.Article{ 234 Author: "Hermione Granger", 235 Date: timestamppb.New(time.Date(1998, time.May, 2, 0, 0, 0, 0, time.UTC)), 236 Title: "Harry Potter vanquishes Voldemort once and for all!", 237 Content: "In a final duel between Harry Potter and Lord Voldemort earlier this evening...", 238 Tags: []string{"HarryPotter", "LordVoldemort"}, 239 Attachments: []*anypb.Any{{ 240 TypeUrl: "google.golang.org.KeyValueAttachment", 241 Value: mustMarshal(&newspb.KeyValueAttachment{ 242 Name: "aliases.txt", 243 Data: map[string]string{ 244 "Harry Potter": "The Boy Who Lived", 245 "Tom Riddle": "Lord Voldemort", 246 }, 247 }), 248 }}, 249 } 250 251 protorange.Range(m.ProtoReflect(), func(p protopath.Values) error { 252 const ( 253 sensitive = "Voldemort" 254 alternative = "[He-Who-Must-Not-Be-Named]" 255 ) 256 257 // Check if there is a sensitive word to redact. 258 last := p.Index(-1) 259 s, ok := last.Value.Interface().(string) 260 if !ok || !strings.Contains(s, sensitive) { 261 return nil 262 } 263 s = strings.Replace(s, sensitive, alternative, -1) 264 265 // Store the redacted string back into the message. 266 beforeLast := p.Index(-2) 267 switch last.Step.Kind() { 268 case protopath.FieldAccessStep: 269 m := beforeLast.Value.Message() 270 fd := last.Step.FieldDescriptor() 271 m.Set(fd, protoreflect.ValueOfString(s)) 272 case protopath.ListIndexStep: 273 ls := beforeLast.Value.List() 274 i := last.Step.ListIndex() 275 ls.Set(i, protoreflect.ValueOfString(s)) 276 case protopath.MapIndexStep: 277 ms := beforeLast.Value.Map() 278 k := last.Step.MapIndex() 279 ms.Set(k, protoreflect.ValueOfString(s)) 280 } 281 return nil 282 }) 283 284 fmt.Println(protojson.Format(m)) 285 286 // Output: 287 // { 288 // "author": "Hermione Granger", 289 // "date": "1998-05-02T00:00:00Z", 290 // "title": "Harry Potter vanquishes [He-Who-Must-Not-Be-Named] once and for all!", 291 // "content": "In a final duel between Harry Potter and Lord [He-Who-Must-Not-Be-Named] earlier this evening...", 292 // "tags": [ 293 // "HarryPotter", 294 // "Lord[He-Who-Must-Not-Be-Named]" 295 // ], 296 // "attachments": [ 297 // { 298 // "@type": "google.golang.org.KeyValueAttachment", 299 // "name": "aliases.txt", 300 // "data": { 301 // "Harry Potter": "The Boy Who Lived", 302 // "Tom Riddle": "Lord [He-Who-Must-Not-Be-Named]" 303 // } 304 // } 305 // ] 306 // } 307} 308