Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/releases/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@
- All successfully read license states exit 0. Configuration, request, decoding, and output failures exit nonzero. No token or database header is sent to `/health`, and no authentication verification is performed. Normal connection selection, timeout, TLS verification, and identity opt-out settings apply.

License installation and key/file configuration remain server responsibilities. Existing `ping -o json` license pass-through is unchanged. Closes [arcli#34](https://github.com/Basekick-Labs/arcli/issues/34).

### Fixed

- Enforce the 64 MiB JSON write input limit when a document ends with whitespace. Oversize input is rejected before upload, including when the bounded read would hide further trailing content.
5 changes: 5 additions & 0 deletions internal/client/msgpackenc.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,11 @@ func JSONToMsgPack(r io.Reader) ([]byte, MsgPackDocSummary, error) {
if _, err := dec.Token(); err != io.EOF {
return nil, sum, fmt.Errorf("invalid JSON: trailing content after the document")
}
// LimitReader also reports EOF after consuming its bound. Count
// trailing whitespace so that EOF cannot hide oversize input.
if cr.n > JSONDocMaxBytes {
return nil, sum, tooBig()
}
converted, err := convertTop(root, &sum)
if err != nil {
return nil, sum, err
Expand Down
31 changes: 31 additions & 0 deletions internal/client/msgpackenc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package client
import (
"bytes"
"encoding/hex"
"io"
"strings"
"testing"
)
Expand Down Expand Up @@ -91,6 +92,36 @@ func TestJSONToMsgPack_RowBatchAndTags(t *testing.T) {
}
}

func TestJSONToMsgPack_TrailingWhitespaceSizeLimit(t *testing.T) {
const document = `{"m":"cpu","fields":{"x":1}}`
padding := strings.Repeat(" ", JSONDocMaxBytes-len(document))
for _, tc := range []struct {
name, suffix string
wantError bool
}{
{"at limit", "", false},
{"one byte over", " ", true},
{"trailing document beyond limit", " {}", true},
} {
t.Run(tc.name, func(t *testing.T) {
in := io.MultiReader(strings.NewReader(document), strings.NewReader(padding), strings.NewReader(tc.suffix))
out, sum, err := JSONToMsgPack(in)
if tc.wantError {
if err == nil || !strings.Contains(err.Error(), "JSON document exceeds") {
t.Fatalf("error = %v, want size-limit error", err)
}
if out != nil || sum != (MsgPackDocSummary{}) {
t.Errorf("oversize input produced output: %x, summary: %+v", out, sum)
}
return
}
if err != nil || len(out) == 0 || sum.Items != 1 || sum.Rows != 1 {
t.Errorf("input at limit: output=%x summary=%+v error=%v", out, sum, err)
}
})
}
}

func TestJSONToMsgPack_Rejections(t *testing.T) {
cases := map[string]string{
`{"columns":{"x":[1]}}`: `"m" (measurement) must be a non-empty string`,
Expand Down
40 changes: 40 additions & 0 deletions internal/commands/write_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package commands

import (
"io"
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"

"github.com/basekick-labs/arcli/internal/client"
)

func TestWriteJSON_RejectsOversizeTrailingWhitespaceBeforeUpload(t *testing.T) {
var requests atomic.Int32
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requests.Add(1)
_, _ = io.Copy(io.Discard, r.Body)
w.WriteHeader(http.StatusNoContent)
}))
defer srv.Close()
writeTestConfig(t, srv.URL, "tok")

const document = `{"m":"cpu","fields":{"x":1}}`
cmd := newWriteCmd()
cmd.SetIn(io.MultiReader(
strings.NewReader(document),
strings.NewReader(strings.Repeat(" ", client.JSONDocMaxBytes-len(document)+1)),
))
out, _, err := execCmd(t, cmd, "--format", "json")
if err == nil || !strings.Contains(err.Error(), "JSON document exceeds") {
t.Errorf("error = %v, want size-limit error", err)
}
if out != "" {
t.Errorf("stdout = %q, want no success output", out)
}
if got := requests.Load(); got != 0 {
t.Errorf("sent %d HTTP requests for rejected input", got)
}
}
Loading