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