Skip to content

Commit 7fd58a7

Browse files
authored
Merge pull request #2558 from carter-ya/codex/configurable-max-body-len
pgconn: add configurable max message body length
2 parents 387c560 + b848d0b commit 7fd58a7

4 files changed

Lines changed: 74 additions & 0 deletions

File tree

pgconn/config.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ type Config struct {
4343
LookupFunc LookupFunc // e.g. net.Resolver.LookupHost
4444
BuildFrontend BuildFrontendFunc
4545

46+
// MaxProtocolMessageBodyLen is the maximum length of a PostgreSQL wire protocol message body in octets. If a
47+
// message body exceeds this length, reading the message will fail with pgproto3.ExceededMaxBodyLenErr. The default
48+
// value is 0, which means no maximum is enforced.
49+
MaxProtocolMessageBodyLen int
50+
4651
// BuildContextWatcherHandler is called to create a ContextWatcherHandler for a connection. The handler is called
4752
// when a context passed to a PgConn method is canceled.
4853
BuildContextWatcherHandler func(*PgConn) ctxwatch.Handler

pgconn/config_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,7 @@ func TestConfigCopyReturnsEqualConfig(t *testing.T) {
841841
connString := "postgres://jack:secret@localhost:5432/mydb?application_name=pgxtest&search_path=myschema&connect_timeout=5"
842842
original, err := pgconn.ParseConfig(connString)
843843
require.NoError(t, err)
844+
original.MaxProtocolMessageBodyLen = 12345
844845

845846
copied := original.Copy()
846847
assertConfigsEqual(t, original, copied, "Test Config.Copy() returns equal config")
@@ -938,6 +939,7 @@ func assertConfigsEqual(t *testing.T, expected, actual *pgconn.Config, testName
938939
assert.Equalf(t, expected.User, actual.User, "%s - User", testName)
939940
assert.Equalf(t, expected.Password, actual.Password, "%s - Password", testName)
940941
assert.Equalf(t, expected.ConnectTimeout, actual.ConnectTimeout, "%s - ConnectTimeout", testName)
942+
assert.Equalf(t, expected.MaxProtocolMessageBodyLen, actual.MaxProtocolMessageBodyLen, "%s - MaxProtocolMessageBodyLen", testName)
941943
assert.Equalf(t, expected.RuntimeParams, actual.RuntimeParams, "%s - RuntimeParams", testName)
942944

943945
// Can't test function equality, so just test that they are set or not.

pgconn/pgconn.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,9 @@ func connectOne(ctx context.Context, config *Config, connectConfig *connectOneCo
392392
pgConn.slowWriteTimer.Stop()
393393
pgConn.bgReaderStarted = make(chan struct{})
394394
pgConn.frontend = config.BuildFrontend(pgConn.bgReader, pgConn.conn)
395+
if config.MaxProtocolMessageBodyLen > 0 {
396+
pgConn.frontend.SetMaxBodyLen(config.MaxProtocolMessageBodyLen)
397+
}
395398

396399
startupMsg := pgproto3.StartupMessage{
397400
ProtocolVersion: maxProtocolVersion,
@@ -2252,6 +2255,9 @@ func Construct(hc *HijackedConn) (*PgConn, error) {
22522255
pgConn.slowWriteTimer.Stop()
22532256
pgConn.bgReaderStarted = make(chan struct{})
22542257
pgConn.frontend = hc.Config.BuildFrontend(pgConn.bgReader, pgConn.conn)
2258+
if hc.Config.MaxProtocolMessageBodyLen > 0 {
2259+
pgConn.frontend.SetMaxBodyLen(hc.Config.MaxProtocolMessageBodyLen)
2260+
}
22552261

22562262
return pgConn, nil
22572263
}

pgconn/pgconn_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,67 @@ func TestConnectConfigRequiresConfigFromParseConfig(t *testing.T) {
874874
require.PanicsWithValue(t, "config must be created by ParseConfig", func() { pgconn.ConnectConfig(ctx, config) })
875875
}
876876

877+
func TestConnectConfigMaxProtocolMessageBodyLen(t *testing.T) {
878+
t.Parallel()
879+
880+
ln, err := net.Listen("tcp", "127.0.0.1:")
881+
require.NoError(t, err)
882+
defer ln.Close()
883+
884+
serverErrChan := make(chan error, 1)
885+
go func() {
886+
defer close(serverErrChan)
887+
888+
conn, err := ln.Accept()
889+
if err != nil {
890+
serverErrChan <- err
891+
return
892+
}
893+
defer conn.Close()
894+
895+
err = conn.SetDeadline(time.Now().Add(5 * time.Second))
896+
if err != nil {
897+
serverErrChan <- err
898+
return
899+
}
900+
901+
backend := pgproto3.NewBackend(conn, conn)
902+
_, err = backend.ReceiveStartupMessage()
903+
if err != nil {
904+
serverErrChan <- err
905+
return
906+
}
907+
908+
// ParameterStatus declares a 6-octet body, which verifies that Config.MaxProtocolMessageBodyLen
909+
// is installed before startup responses are received.
910+
_, err = conn.Write([]byte{'S', 0, 0, 0, 10})
911+
if err != nil {
912+
serverErrChan <- err
913+
return
914+
}
915+
}()
916+
917+
host, port, _ := strings.Cut(ln.Addr().String(), ":")
918+
connStr := fmt.Sprintf("sslmode=disable host=%s port=%s", host, port)
919+
920+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
921+
defer cancel()
922+
923+
config, err := pgconn.ParseConfig(connStr)
924+
require.NoError(t, err)
925+
config.MaxProtocolMessageBodyLen = 5
926+
927+
conn, err := pgconn.ConnectConfig(ctx, config)
928+
require.Nil(t, conn)
929+
930+
var bodyLenErr *pgproto3.ExceededMaxBodyLenErr
931+
require.ErrorAs(t, err, &bodyLenErr)
932+
assert.Equal(t, 5, bodyLenErr.MaxExpectedBodyLen)
933+
assert.Equal(t, 6, bodyLenErr.ActualBodyLen)
934+
935+
require.NoError(t, <-serverErrChan)
936+
}
937+
877938
func TestConnPrepareSyntaxError(t *testing.T) {
878939
t.Parallel()
879940

0 commit comments

Comments
 (0)