From 0e1bc2418e5537930c722b0c43f623da35c01748 Mon Sep 17 00:00:00 2001 From: Arkadiy Tetelman Date: Fri, 12 Jun 2026 16:33:00 -0700 Subject: [PATCH] Add escaping --- pkg/protodump/fixtures/escaping.proto2 | 13 +++++++ pkg/protodump/proto.go | 50 ++++++++++++++++++++------ pkg/protodump/proto_test.go | 9 ++--- 3 files changed, 58 insertions(+), 14 deletions(-) create mode 100644 pkg/protodump/fixtures/escaping.proto2 diff --git a/pkg/protodump/fixtures/escaping.proto2 b/pkg/protodump/fixtures/escaping.proto2 new file mode 100644 index 0000000..8d0083d --- /dev/null +++ b/pkg/protodump/fixtures/escaping.proto2 @@ -0,0 +1,13 @@ +syntax = "proto2"; + +package escaping; + +option java_package = "a\"b\\c"; +option go_package = "./;escaping"; + +message M { + reserved "ev\"il"; + optional string note = 1 [default = "evil\";\n}\nmessage Injected {\n string pwned = 1;\n}\n//"]; + optional bytes blob = 2 [default = "A\377\001hi"]; +} + diff --git a/pkg/protodump/proto.go b/pkg/protodump/proto.go index d9261ad..d70fa06 100644 --- a/pkg/protodump/proto.go +++ b/pkg/protodump/proto.go @@ -40,6 +40,35 @@ func (pd *ProtoDefinition) write(s string) { pd.builder.WriteString(s) } +func escapeProtoString(s string) string { + var b strings.Builder + for i := 0; i < len(s); i++ { + switch c := s[i]; { + case c == '\\': + b.WriteString(`\\`) + case c == '"': + b.WriteString(`\"`) + case c == '\n': + b.WriteString(`\n`) + case c == '\r': + b.WriteString(`\r`) + case c == '\t': + b.WriteString(`\t`) + case c < 0x20 || c >= 0x7f: + fmt.Fprintf(&b, `\%03o`, c) + default: + b.WriteByte(c) + } + } + return b.String() +} + +func (pd *ProtoDefinition) writeQuoted(s string) { + pd.write("\"") + pd.write(escapeProtoString(s)) + pd.write("\"") +} + func (pd *ProtoDefinition) String() string { return pd.builder.String() } @@ -143,7 +172,9 @@ func (pd *ProtoDefinition) writeField(field protoreflect.FieldDescriptor) { pd.write(" [default = ") kind := field.Kind().String() if kind == "string" { - pd.write(fmt.Sprintf("\"%s\"", field.Default().String())) + pd.writeQuoted(field.Default().String()) + } else if kind == "bytes" { + pd.writeQuoted(string(field.Default().Bytes())) } else if kind == "enum" { pd.write(string(field.DefaultEnumValue().Name())) } else { @@ -181,9 +212,9 @@ func (pd *ProtoDefinition) writeMessage(message protoreflect.MessageDescriptor) for i := 0; i < message.ReservedNames().Len(); i++ { name := message.ReservedNames().Get(i) - pd.writeIndented("reserved \"") - pd.write(string(name)) - pd.write("\";\n") + pd.writeIndented("reserved ") + pd.writeQuoted(string(name)) + pd.write(";\n") } for i := 0; i < message.ReservedRanges().Len(); i++ { @@ -234,17 +265,16 @@ func (pd *ProtoDefinition) writeImport(fileImport protoreflect.FileImport) { if fileImport.IsPublic { pd.write("public ") } - pd.write("\"") - pd.write(fileImport.Path()) - pd.write("\";\n") + pd.writeQuoted(fileImport.Path()) + pd.write(";\n") } func (pd *ProtoDefinition) writeStringFileOptions(name string, value string) { pd.write("option ") pd.write(name) - pd.write(" = \"") - pd.write(strings.ReplaceAll(value, "\\", "\\\\")) - pd.write("\";\n") + pd.write(" = ") + pd.writeQuoted(value) + pd.write(";\n") } func (pd *ProtoDefinition) writeBoolFileOptions(name string, value bool) { diff --git a/pkg/protodump/proto_test.go b/pkg/protodump/proto_test.go index 8f35d3c..a65af15 100644 --- a/pkg/protodump/proto_test.go +++ b/pkg/protodump/proto_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/descriptorpb" ) @@ -44,19 +45,19 @@ func convertProtoToFileDescriptor(filePath string) (*descriptorpb.FileDescriptor func TestDefinitions(t *testing.T) { files, err := os.ReadDir(FIXTURES) - assert.NoError(t, err) + require.NoError(t, err) for _, file := range files { t.Run(file.Name(), func(t *testing.T) { filePath := path.Join(FIXTURES, file.Name()) descriptor, err := convertProtoToFileDescriptor(filePath) - assert.NoError(t, err) + require.NoError(t, err) expected, err := os.ReadFile(filePath) - assert.NoError(t, err) + require.NoError(t, err) actual, err := NewFromDescriptor(descriptor) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, string(expected), actual.String()) })