From a5f1f9a7c0e56593bd5fbb3ee5f6d15b5890a335 Mon Sep 17 00:00:00 2001 From: Georgii Imeshkenov Date: Tue, 22 Sep 2026 20:23:28 +0300 Subject: [PATCH 1/2] fix: do not escaping CONNECT and CONNECTED headers --- frame/writer.go | 39 ++++++++++++++++++++++++++-- frame/writer_test.go | 62 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 2 deletions(-) diff --git a/frame/writer.go b/frame/writer.go index 7c6e83e..a246415 100644 --- a/frame/writer.go +++ b/frame/writer.go @@ -3,6 +3,7 @@ package frame import ( "bufio" "io" + "strings" ) // slices used to write frames @@ -27,6 +28,33 @@ func NewWriterSize(writer io.Writer, bufferSize int) *Writer { return &Writer{writer: bufio.NewWriterSize(writer, bufferSize)} } +func headersShouldBeEscaped(command string) bool { + // STOMP 1.2 excludes CONNECT and CONNECTED frames from header escaping. + return command != CONNECT && command != CONNECTED +} + +func validateUnescapedHeaders(header *Header) error { + if header == nil { + return nil + } + + for i := 0; i < header.Len(); i++ { + key, value := header.GetAt(i) + if key == "" || strings.ContainsAny(key, ":\r\n\x00") || strings.ContainsAny(value, "\r\n\x00") { + return ErrInvalidFrameFormat + } + } + + return nil +} + +func (w *Writer) writeHeaderString(value string, escape bool) (int, error) { + if escape { + return replacerForEncodeValue.WriteString(w.writer, value) + } + return w.writer.WriteString(value) +} + // Write the contents of a frame to the underlying io.Writer. func (w *Writer) Write(f *Frame) error { var err error @@ -38,6 +66,13 @@ func (w *Writer) Write(f *Frame) error { return err } } else { + needEscapeHeaders := headersShouldBeEscaped(f.Command) + if !needEscapeHeaders { + if err = validateUnescapedHeaders(f.Header); err != nil { + return err + } + } + _, err = w.writer.Write([]byte(f.Command)) if err != nil { return err @@ -53,7 +88,7 @@ func (w *Writer) Write(f *Frame) error { for i := 0; i < f.Header.Len(); i++ { key, value := f.Header.GetAt(i) //println(" ", key, ":", value) - _, err = replacerForEncodeValue.WriteString(w.writer, key) + _, err = w.writeHeaderString(key, needEscapeHeaders) if err != nil { return err } @@ -61,7 +96,7 @@ func (w *Writer) Write(f *Frame) error { if err != nil { return err } - _, err = replacerForEncodeValue.WriteString(w.writer, value) + _, err = w.writeHeaderString(value, needEscapeHeaders) if err != nil { return err } diff --git a/frame/writer_test.go b/frame/writer_test.go index ea5a1f5..52a95b2 100644 --- a/frame/writer_test.go +++ b/frame/writer_test.go @@ -31,6 +31,68 @@ func (s *WriterSuite) TestWrites(c *C) { } } +func (s *WriterSuite) TestConnectHeadersAreNotEscaped(c *C) { + tests := []struct { + frame *Frame + expected string + }{ + { + frame: New(CONNECT, + "login", `user\name`, + "passcode", `=x/xx-test:password:xx+xxx`, + ), + expected: "CONNECT\n" + + "login:user\\name\n" + + "passcode:=x/xx-test:password:xx+xxx\n\n\x00", + }, + { + frame: New(CONNECTED, "server", `broker:1\main`), + expected: "CONNECTED\nserver:broker:1\\main\n\n\x00", + }, + } + + for _, test := range tests { + var buffer bytes.Buffer + err := NewWriter(&buffer).Write(test.frame) + c.Assert(err, IsNil) + c.Check(buffer.String(), Equals, test.expected) + } +} + +func (s *WriterSuite) TestOtherFrameHeadersAreEscaped(c *C) { + for _, command := range []string{STOMP, SEND} { + var buffer bytes.Buffer + err := NewWriter(&buffer).Write(New(command, "key", `abc:def\ghi`)) + c.Assert(err, IsNil) + c.Check(buffer.String(), Equals, command+"\nkey:abc\\cdef\\\\ghi\n\n\x00") + } +} + +func (s *WriterSuite) TestInvalidUnescapedHeadersAreRejected(c *C) { + tests := []struct { + key string + value string + }{ + {key: "", value: "value"}, + {key: "bad:key", value: "value"}, + {key: "bad\rkey", value: "value"}, + {key: "bad\nkey", value: "value"}, + {key: "bad\x00key", value: "value"}, + {key: "key", value: "bad\rvalue"}, + {key: "key", value: "bad\nvalue"}, + {key: "key", value: "bad\x00value"}, + } + + for _, command := range []string{CONNECT, CONNECTED} { + for _, test := range tests { + var buffer bytes.Buffer + err := NewWriter(&buffer).Write(New(command, test.key, test.value)) + c.Check(err, Equals, ErrInvalidFrameFormat) + c.Check(buffer.String(), Equals, "") + } + } +} + func writeToBufferAndCheck(c *C, frameText string) { reader := NewReader(strings.NewReader(frameText)) From e9c5eb2111c1884526b43228b2f6c7a394d8019a Mon Sep 17 00:00:00 2001 From: Georgii Imeshkenov Date: Wed, 23 Sep 2026 23:07:36 +0300 Subject: [PATCH 2/2] fix: resolving conflict; --- frame/writer_test.go | 56 ++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/frame/writer_test.go b/frame/writer_test.go index 6c120ee..f9d41ea 100644 --- a/frame/writer_test.go +++ b/frame/writer_test.go @@ -28,12 +28,14 @@ func TestWriterWrites(t *testing.T) { } } -func (s *WriterSuite) TestConnectHeadersAreNotEscaped(c *C) { +func TestWriterConnectHeadersAreNotEscaped(t *testing.T) { tests := []struct { + name string frame *Frame expected string }{ { + name: "CONNECT", frame: New(CONNECT, "login", `user\name`, "passcode", `=x/xx-test:password:xx+xxx`, @@ -43,49 +45,57 @@ func (s *WriterSuite) TestConnectHeadersAreNotEscaped(c *C) { "passcode:=x/xx-test:password:xx+xxx\n\n\x00", }, { + name: "CONNECTED", frame: New(CONNECTED, "server", `broker:1\main`), expected: "CONNECTED\nserver:broker:1\\main\n\n\x00", }, } for _, test := range tests { - var buffer bytes.Buffer - err := NewWriter(&buffer).Write(test.frame) - c.Assert(err, IsNil) - c.Check(buffer.String(), Equals, test.expected) + t.Run(test.name, func(t *testing.T) { + var buffer bytes.Buffer + err := NewWriter(&buffer).Write(test.frame) + require.NoError(t, err) + require.Equal(t, test.expected, buffer.String()) + }) } } -func (s *WriterSuite) TestOtherFrameHeadersAreEscaped(c *C) { +func TestWriterOtherFrameHeadersAreEscaped(t *testing.T) { for _, command := range []string{STOMP, SEND} { - var buffer bytes.Buffer - err := NewWriter(&buffer).Write(New(command, "key", `abc:def\ghi`)) - c.Assert(err, IsNil) - c.Check(buffer.String(), Equals, command+"\nkey:abc\\cdef\\\\ghi\n\n\x00") + t.Run(command, func(t *testing.T) { + var buffer bytes.Buffer + err := NewWriter(&buffer).Write(New(command, "key", `abc:def\ghi`)) + require.NoError(t, err) + require.Equal(t, command+"\nkey:abc\\cdef\\\\ghi\n\n\x00", buffer.String()) + }) } } -func (s *WriterSuite) TestInvalidUnescapedHeadersAreRejected(c *C) { +func TestWriterInvalidUnescapedHeadersAreRejected(t *testing.T) { tests := []struct { + name string key string value string }{ - {key: "", value: "value"}, - {key: "bad:key", value: "value"}, - {key: "bad\rkey", value: "value"}, - {key: "bad\nkey", value: "value"}, - {key: "bad\x00key", value: "value"}, - {key: "key", value: "bad\rvalue"}, - {key: "key", value: "bad\nvalue"}, - {key: "key", value: "bad\x00value"}, + {name: "empty key", key: "", value: "value"}, + {name: "colon in key", key: "bad:key", value: "value"}, + {name: "carriage return in key", key: "bad\rkey", value: "value"}, + {name: "line feed in key", key: "bad\nkey", value: "value"}, + {name: "NUL in key", key: "bad\x00key", value: "value"}, + {name: "carriage return in value", key: "key", value: "bad\rvalue"}, + {name: "line feed in value", key: "key", value: "bad\nvalue"}, + {name: "NUL in value", key: "key", value: "bad\x00value"}, } for _, command := range []string{CONNECT, CONNECTED} { for _, test := range tests { - var buffer bytes.Buffer - err := NewWriter(&buffer).Write(New(command, test.key, test.value)) - c.Check(err, Equals, ErrInvalidFrameFormat) - c.Check(buffer.String(), Equals, "") + t.Run(command+"/"+test.name, func(t *testing.T) { + var buffer bytes.Buffer + err := NewWriter(&buffer).Write(New(command, test.key, test.value)) + require.ErrorIs(t, err, ErrInvalidFrameFormat) + require.Empty(t, buffer.String()) + }) } } }