Skip to content

Commit 41ae846

Browse files
authored
feat(client): add table and column name length validation
2 parents 457df4c + ba322ab commit 41ae846

File tree

2 files changed

+90
-15
lines changed

2 files changed

+90
-15
lines changed

sender.go

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ import (
4848
// chars found in table or column name.
4949
var ErrInvalidMsg = errors.New("invalid message")
5050

51+
const (
52+
defaultBufferCapacity = 128 * 1024
53+
defaultFileNameLimit = 127
54+
)
55+
5156
type tlsMode int64
5257

5358
const (
@@ -62,18 +67,19 @@ const (
6267
// Each sender corresponds to a single TCP connection. A sender
6368
// should not be called concurrently by multiple goroutines.
6469
type LineSender struct {
65-
address string
66-
tlsMode tlsMode
67-
keyId string // Erased once auth is done.
68-
key string // Erased once auth is done.
69-
bufCap int
70-
conn net.Conn
71-
buf *buffer
72-
lastMsgPos int
73-
lastErr error
74-
hasTable bool
75-
hasTags bool
76-
hasFields bool
70+
address string
71+
tlsMode tlsMode
72+
keyId string // Erased once auth is done.
73+
key string // Erased once auth is done.
74+
bufCap int
75+
fileNameLimit int
76+
conn net.Conn
77+
buf *buffer
78+
lastMsgPos int
79+
lastErr error
80+
hasTable bool
81+
hasTags bool
82+
hasFields bool
7783
}
7884

7985
// LineSenderOption defines line sender option.
@@ -126,6 +132,18 @@ func WithBufferCapacity(capacity int) LineSenderOption {
126132
}
127133
}
128134

135+
// WithFileNameLimit sets maximum file name length in chars
136+
// allowed by the server. Affects maximum table and column name
137+
// lengths accepted by the sender. Should be set to the same value
138+
// as on the server. Defaults to 127.
139+
func WithFileNameLimit(limit int) LineSenderOption {
140+
return func(s *LineSender) {
141+
if limit > 0 {
142+
s.fileNameLimit = limit
143+
}
144+
}
145+
}
146+
129147
// NewLineSender creates new InfluxDB Line Protocol (ILP) sender. Each
130148
// sender corresponds to a single TCP connection. Sender should
131149
// not be called concurrently by multiple goroutines.
@@ -138,9 +156,10 @@ func NewLineSender(ctx context.Context, opts ...LineSenderOption) (*LineSender,
138156
)
139157

140158
s := &LineSender{
141-
address: "127.0.0.1:9009",
142-
bufCap: 128 * 1024,
143-
tlsMode: noTls,
159+
address: "127.0.0.1:9009",
160+
bufCap: defaultBufferCapacity,
161+
fileNameLimit: defaultFileNameLimit,
162+
tlsMode: noTls,
144163
}
145164
for _, opt := range opts {
146165
opt(s)
@@ -375,6 +394,11 @@ func (s *LineSender) writeTableName(str string) error {
375394
if str == "" {
376395
return fmt.Errorf("table name cannot be empty: %w", ErrInvalidMsg)
377396
}
397+
// We use string length in bytes as an approximation. That's to
398+
// avoid calculating the number of runes.
399+
if len(str) > s.fileNameLimit {
400+
return fmt.Errorf("table name length exceeds the limit: %w", ErrInvalidMsg)
401+
}
378402
// Since we're interested in ASCII chars, it's fine to iterate
379403
// through bytes instead of runes.
380404
for i := 0; i < len(str); i++ {
@@ -470,6 +494,11 @@ func (s *LineSender) writeColumnName(str string) error {
470494
if str == "" {
471495
return fmt.Errorf("column name cannot be empty: %w", ErrInvalidMsg)
472496
}
497+
// We use string length in bytes as an approximation. That's to
498+
// avoid calculating the number of runes.
499+
if len(str) > s.fileNameLimit {
500+
return fmt.Errorf("column name length exceeds the limit: %w", ErrInvalidMsg)
501+
}
473502
// Since we're interested in ASCII chars, it's fine to iterate
474503
// through bytes instead of runes.
475504
for i := 0; i < len(str); i++ {

sender_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,52 @@ func TestFloat64Serialization(t *testing.T) {
193193
}
194194
}
195195

196+
func TestErrorOnLengthyNames(t *testing.T) {
197+
const nameLimit = 42
198+
199+
var (
200+
lengthyStr = strings.Repeat("a", nameLimit+1)
201+
ctx = context.Background()
202+
)
203+
204+
testCases := []struct {
205+
name string
206+
writerFn writerFn
207+
expectedErrMsg string
208+
}{
209+
{
210+
"lengthy table name",
211+
func(s *qdb.LineSender) error {
212+
return s.Table(lengthyStr).StringColumn("str_col", "foo").AtNow(ctx)
213+
},
214+
"table name length exceeds the limit",
215+
},
216+
{
217+
"lengthy column name",
218+
func(s *qdb.LineSender) error {
219+
return s.Table(testTable).StringColumn(lengthyStr, "foo").AtNow(ctx)
220+
},
221+
"column name length exceeds the limit",
222+
},
223+
}
224+
225+
for _, tc := range testCases {
226+
t.Run(tc.name, func(t *testing.T) {
227+
srv, err := newTestServer(readAndDiscard)
228+
assert.NoError(t, err)
229+
230+
sender, err := qdb.NewLineSender(ctx, qdb.WithAddress(srv.addr), qdb.WithFileNameLimit(nameLimit))
231+
assert.NoError(t, err)
232+
233+
err = tc.writerFn(sender)
234+
assert.ErrorContains(t, err, tc.expectedErrMsg)
235+
236+
sender.Close()
237+
srv.close()
238+
})
239+
}
240+
}
241+
196242
func TestErrorOnMissingTableCall(t *testing.T) {
197243
ctx := context.Background()
198244

0 commit comments

Comments
 (0)