Browse code

API: properly handle invalid JSON to return a 400 status

The API did not treat invalid JSON payloads as a 400 error, as a result
returning a 500 error;

Before this change, an invalid JSON body would return a 500 error;

```bash
curl -v \
--unix-socket /var/run/docker.sock \
-X POST \
"http://localhost/v1.30/networks/create" \
-H "Content-Type: application/json" \
-d '{invalid json'
```

```
> POST /v1.30/networks/create HTTP/1.1
> Host: localhost
> User-Agent: curl/7.52.1
> Accept: */*
> Content-Type: application/json
> Content-Length: 13
>
* upload completely sent off: 13 out of 13 bytes
< HTTP/1.1 500 Internal Server Error
< Api-Version: 1.40
< Content-Type: application/json
< Docker-Experimental: false
< Ostype: linux
< Server: Docker/dev (linux)
< Date: Mon, 05 Nov 2018 11:55:20 GMT
< Content-Length: 79
<
{"message":"invalid character 'i' looking for beginning of object key string"}
```

Empty request:

```bash
curl -v \
--unix-socket /var/run/docker.sock \
-X POST \
"http://localhost/v1.30/networks/create" \
-H "Content-Type: application/json"
```

```
> POST /v1.30/networks/create HTTP/1.1
> Host: localhost
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Type: application/json
>
< HTTP/1.1 500 Internal Server Error
< Api-Version: 1.38
< Content-Length: 18
< Content-Type: application/json
< Date: Mon, 05 Nov 2018 12:00:18 GMT
< Docker-Experimental: true
< Ostype: linux
< Server: Docker/18.06.1-ce (linux)
<
{"message":"EOF"}
```

After this change, a 400 is returned;

```bash
curl -v \
--unix-socket /var/run/docker.sock \
-X POST \
"http://localhost/v1.30/networks/create" \
-H "Content-Type: application/json" \
-d '{invalid json'
```

```
> POST /v1.30/networks/create HTTP/1.1
> Host: localhost
> User-Agent: curl/7.52.1
> Accept: */*
> Content-Type: application/json
> Content-Length: 13
>
* upload completely sent off: 13 out of 13 bytes
< HTTP/1.1 400 Bad Request
< Api-Version: 1.40
< Content-Type: application/json
< Docker-Experimental: false
< Ostype: linux
< Server: Docker/dev (linux)
< Date: Mon, 05 Nov 2018 11:57:15 GMT
< Content-Length: 79
<
{"message":"invalid character 'i' looking for beginning of object key string"}
```

Empty request:

```bash
curl -v \
--unix-socket /var/run/docker.sock \
-X POST \
"http://localhost/v1.30/networks/create" \
-H "Content-Type: application/json"
```

```
> POST /v1.30/networks/create HTTP/1.1
> Host: localhost
> User-Agent: curl/7.52.1
> Accept: */*
> Content-Type: application/json
>
< HTTP/1.1 400 Bad Request
< Api-Version: 1.40
< Content-Type: application/json
< Docker-Experimental: false
< Ostype: linux
< Server: Docker/dev (linux)
< Date: Mon, 05 Nov 2018 11:59:22 GMT
< Content-Length: 49
<
{"message":"got EOF while reading request body"}
```

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2018/11/05 22:50:33
Showing 11 changed files
... ...
@@ -6,12 +6,14 @@ import (
6 6
 	"context"
7 7
 	"encoding/base64"
8 8
 	"encoding/json"
9
+	"errors"
9 10
 	"io"
10 11
 	"net/http"
11 12
 
12 13
 	"github.com/docker/docker/api/server/httputils"
13 14
 	"github.com/docker/docker/api/types"
14 15
 	"github.com/docker/docker/api/types/versions"
16
+	"github.com/docker/docker/errdefs"
15 17
 	gddohttputil "github.com/golang/gddo/httputil"
16 18
 )
17 19
 
... ...
@@ -37,7 +39,10 @@ func (s *containerRouter) postContainersCopy(ctx context.Context, w http.Respons
37 37
 
38 38
 	cfg := types.CopyConfig{}
39 39
 	if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
40
-		return err
40
+		if err == io.EOF {
41
+			return errdefs.InvalidParameter(errors.New("got EOF while reading request body"))
42
+		}
43
+		return errdefs.InvalidParameter(err)
41 44
 	}
42 45
 
43 46
 	if cfg.Resource == "" {
... ...
@@ -3,6 +3,7 @@ package container // import "github.com/docker/docker/api/server/router/containe
3 3
 import (
4 4
 	"context"
5 5
 	"encoding/json"
6
+	"errors"
6 7
 	"fmt"
7 8
 	"io"
8 9
 	"net/http"
... ...
@@ -44,7 +45,10 @@ func (s *containerRouter) postContainerExecCreate(ctx context.Context, w http.Re
44 44
 
45 45
 	execConfig := &types.ExecConfig{}
46 46
 	if err := json.NewDecoder(r.Body).Decode(execConfig); err != nil {
47
-		return err
47
+		if err == io.EOF {
48
+			return errdefs.InvalidParameter(errors.New("got EOF while reading request body"))
49
+		}
50
+		return errdefs.InvalidParameter(err)
48 51
 	}
49 52
 
50 53
 	if len(execConfig.Cmd) == 0 {
... ...
@@ -84,7 +88,10 @@ func (s *containerRouter) postContainerExecStart(ctx context.Context, w http.Res
84 84
 
85 85
 	execStartCheck := &types.ExecStartCheck{}
86 86
 	if err := json.NewDecoder(r.Body).Decode(execStartCheck); err != nil {
87
-		return err
87
+		if err == io.EOF {
88
+			return errdefs.InvalidParameter(errors.New("got EOF while reading request body"))
89
+		}
90
+		return errdefs.InvalidParameter(err)
88 91
 	}
89 92
 
90 93
 	if exists, err := s.backend.ExecExists(execName); !exists {
... ...
@@ -3,6 +3,7 @@ package network // import "github.com/docker/docker/api/server/router/network"
3 3
 import (
4 4
 	"context"
5 5
 	"encoding/json"
6
+	"io"
6 7
 	"net/http"
7 8
 	"strconv"
8 9
 	"strings"
... ...
@@ -215,7 +216,10 @@ func (n *networkRouter) postNetworkCreate(ctx context.Context, w http.ResponseWr
215 215
 	}
216 216
 
217 217
 	if err := json.NewDecoder(r.Body).Decode(&create); err != nil {
218
-		return err
218
+		if err == io.EOF {
219
+			return errdefs.InvalidParameter(errors.New("got EOF while reading request body"))
220
+		}
221
+		return errdefs.InvalidParameter(err)
219 222
 	}
220 223
 
221 224
 	if nws, err := n.cluster.GetNetworksByName(create.Name); err == nil && len(nws) > 0 {
... ...
@@ -261,7 +265,10 @@ func (n *networkRouter) postNetworkConnect(ctx context.Context, w http.ResponseW
261 261
 	}
262 262
 
263 263
 	if err := json.NewDecoder(r.Body).Decode(&connect); err != nil {
264
-		return err
264
+		if err == io.EOF {
265
+			return errdefs.InvalidParameter(errors.New("got EOF while reading request body"))
266
+		}
267
+		return errdefs.InvalidParameter(err)
265 268
 	}
266 269
 
267 270
 	// Unlike other operations, we does not check ambiguity of the name/ID here.
... ...
@@ -282,7 +289,10 @@ func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.Respon
282 282
 	}
283 283
 
284 284
 	if err := json.NewDecoder(r.Body).Decode(&disconnect); err != nil {
285
-		return err
285
+		if err == io.EOF {
286
+			return errdefs.InvalidParameter(errors.New("got EOF while reading request body"))
287
+		}
288
+		return errdefs.InvalidParameter(err)
286 289
 	}
287 290
 
288 291
 	return n.backend.DisconnectContainerFromNetwork(disconnect.Container, vars["id"], disconnect.Force)
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"context"
5 5
 	"encoding/base64"
6 6
 	"encoding/json"
7
+	"io"
7 8
 	"net/http"
8 9
 	"strconv"
9 10
 	"strings"
... ...
@@ -12,6 +13,7 @@ import (
12 12
 	"github.com/docker/docker/api/server/httputils"
13 13
 	"github.com/docker/docker/api/types"
14 14
 	"github.com/docker/docker/api/types/filters"
15
+	"github.com/docker/docker/errdefs"
15 16
 	"github.com/docker/docker/pkg/ioutils"
16 17
 	"github.com/docker/docker/pkg/streamformatter"
17 18
 	"github.com/pkg/errors"
... ...
@@ -276,7 +278,10 @@ func (pr *pluginRouter) pushPlugin(ctx context.Context, w http.ResponseWriter, r
276 276
 func (pr *pluginRouter) setPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
277 277
 	var args []string
278 278
 	if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
279
-		return err
279
+		if err == io.EOF {
280
+			return errdefs.InvalidParameter(errors.New("got EOF while reading request body"))
281
+		}
282
+		return errdefs.InvalidParameter(err)
280 283
 	}
281 284
 	if err := pr.backend.Set(vars["name"], args); err != nil {
282 285
 		return err
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"context"
5 5
 	"encoding/json"
6 6
 	"fmt"
7
+	"io"
7 8
 	"net/http"
8 9
 	"strconv"
9 10
 
... ...
@@ -21,7 +22,10 @@ import (
21 21
 func (sr *swarmRouter) initCluster(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
22 22
 	var req types.InitRequest
23 23
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
24
-		return err
24
+		if err == io.EOF {
25
+			return errdefs.InvalidParameter(errors.New("got EOF while reading request body"))
26
+		}
27
+		return errdefs.InvalidParameter(err)
25 28
 	}
26 29
 	nodeID, err := sr.backend.Init(req)
27 30
 	if err != nil {
... ...
@@ -34,7 +38,10 @@ func (sr *swarmRouter) initCluster(ctx context.Context, w http.ResponseWriter, r
34 34
 func (sr *swarmRouter) joinCluster(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
35 35
 	var req types.JoinRequest
36 36
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
37
-		return err
37
+		if err == io.EOF {
38
+			return errdefs.InvalidParameter(errors.New("got EOF while reading request body"))
39
+		}
40
+		return errdefs.InvalidParameter(err)
38 41
 	}
39 42
 	return sr.backend.Join(req)
40 43
 }
... ...
@@ -61,7 +68,10 @@ func (sr *swarmRouter) inspectCluster(ctx context.Context, w http.ResponseWriter
61 61
 func (sr *swarmRouter) updateCluster(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
62 62
 	var swarm types.Spec
63 63
 	if err := json.NewDecoder(r.Body).Decode(&swarm); err != nil {
64
-		return err
64
+		if err == io.EOF {
65
+			return errdefs.InvalidParameter(errors.New("got EOF while reading request body"))
66
+		}
67
+		return errdefs.InvalidParameter(err)
65 68
 	}
66 69
 
67 70
 	rawVersion := r.URL.Query().Get("version")
... ...
@@ -112,7 +122,10 @@ func (sr *swarmRouter) updateCluster(ctx context.Context, w http.ResponseWriter,
112 112
 func (sr *swarmRouter) unlockCluster(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
113 113
 	var req types.UnlockRequest
114 114
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
115
-		return err
115
+		if err == io.EOF {
116
+			return errdefs.InvalidParameter(errors.New("got EOF while reading request body"))
117
+		}
118
+		return errdefs.InvalidParameter(err)
116 119
 	}
117 120
 
118 121
 	if err := sr.backend.UnlockSwarm(req); err != nil {
... ...
@@ -175,7 +188,10 @@ func (sr *swarmRouter) getService(ctx context.Context, w http.ResponseWriter, r
175 175
 func (sr *swarmRouter) createService(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
176 176
 	var service types.ServiceSpec
177 177
 	if err := json.NewDecoder(r.Body).Decode(&service); err != nil {
178
-		return err
178
+		if err == io.EOF {
179
+			return errdefs.InvalidParameter(errors.New("got EOF while reading request body"))
180
+		}
181
+		return errdefs.InvalidParameter(err)
179 182
 	}
180 183
 
181 184
 	// Get returns "" if the header does not exist
... ...
@@ -207,7 +223,10 @@ func (sr *swarmRouter) createService(ctx context.Context, w http.ResponseWriter,
207 207
 func (sr *swarmRouter) updateService(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
208 208
 	var service types.ServiceSpec
209 209
 	if err := json.NewDecoder(r.Body).Decode(&service); err != nil {
210
-		return err
210
+		if err == io.EOF {
211
+			return errdefs.InvalidParameter(errors.New("got EOF while reading request body"))
212
+		}
213
+		return errdefs.InvalidParameter(err)
211 214
 	}
212 215
 
213 216
 	rawVersion := r.URL.Query().Get("version")
... ...
@@ -309,7 +328,10 @@ func (sr *swarmRouter) getNode(ctx context.Context, w http.ResponseWriter, r *ht
309 309
 func (sr *swarmRouter) updateNode(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
310 310
 	var node types.NodeSpec
311 311
 	if err := json.NewDecoder(r.Body).Decode(&node); err != nil {
312
-		return err
312
+		if err == io.EOF {
313
+			return errdefs.InvalidParameter(errors.New("got EOF while reading request body"))
314
+		}
315
+		return errdefs.InvalidParameter(err)
313 316
 	}
314 317
 
315 318
 	rawVersion := r.URL.Query().Get("version")
... ...
@@ -388,7 +410,10 @@ func (sr *swarmRouter) getSecrets(ctx context.Context, w http.ResponseWriter, r
388 388
 func (sr *swarmRouter) createSecret(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
389 389
 	var secret types.SecretSpec
390 390
 	if err := json.NewDecoder(r.Body).Decode(&secret); err != nil {
391
-		return err
391
+		if err == io.EOF {
392
+			return errdefs.InvalidParameter(errors.New("got EOF while reading request body"))
393
+		}
394
+		return errdefs.InvalidParameter(err)
392 395
 	}
393 396
 	version := httputils.VersionFromContext(ctx)
394 397
 	if secret.Templating != nil && versions.LessThan(version, "1.37") {
... ...
@@ -426,6 +451,9 @@ func (sr *swarmRouter) getSecret(ctx context.Context, w http.ResponseWriter, r *
426 426
 func (sr *swarmRouter) updateSecret(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
427 427
 	var secret types.SecretSpec
428 428
 	if err := json.NewDecoder(r.Body).Decode(&secret); err != nil {
429
+		if err == io.EOF {
430
+			return errdefs.InvalidParameter(errors.New("got EOF while reading request body"))
431
+		}
429 432
 		return errdefs.InvalidParameter(err)
430 433
 	}
431 434
 
... ...
@@ -459,7 +487,10 @@ func (sr *swarmRouter) getConfigs(ctx context.Context, w http.ResponseWriter, r
459 459
 func (sr *swarmRouter) createConfig(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
460 460
 	var config types.ConfigSpec
461 461
 	if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
462
-		return err
462
+		if err == io.EOF {
463
+			return errdefs.InvalidParameter(errors.New("got EOF while reading request body"))
464
+		}
465
+		return errdefs.InvalidParameter(err)
463 466
 	}
464 467
 
465 468
 	version := httputils.VersionFromContext(ctx)
... ...
@@ -498,6 +529,9 @@ func (sr *swarmRouter) getConfig(ctx context.Context, w http.ResponseWriter, r *
498 498
 func (sr *swarmRouter) updateConfig(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
499 499
 	var config types.ConfigSpec
500 500
 	if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
501
+		if err == io.EOF {
502
+			return errdefs.InvalidParameter(errors.New("got EOF while reading request body"))
503
+		}
501 504
 		return errdefs.InvalidParameter(err)
502 505
 	}
503 506
 
... ...
@@ -56,7 +56,7 @@ func (v *volumeRouter) postVolumesCreate(ctx context.Context, w http.ResponseWri
56 56
 		if err == io.EOF {
57 57
 			return errdefs.InvalidParameter(errors.New("got EOF while reading request body"))
58 58
 		}
59
-		return err
59
+		return errdefs.InvalidParameter(err)
60 60
 	}
61 61
 
62 62
 	volume, err := v.backend.Create(ctx, req.Name, req.Driver, opts.WithCreateOptions(req.DriverOpts), opts.WithCreateLabels(req.Labels))
63 63
new file mode 100644
... ...
@@ -0,0 +1,42 @@
0
+package container // import "github.com/docker/docker/integration/container"
1
+
2
+import (
3
+	"net/http"
4
+	"testing"
5
+
6
+	"github.com/docker/docker/internal/test/request"
7
+	"gotest.tools/assert"
8
+	is "gotest.tools/assert/cmp"
9
+)
10
+
11
+func TestContainerInvalidJSON(t *testing.T) {
12
+	defer setupTest(t)()
13
+
14
+	endpoints := []string{
15
+		"/containers/foobar/copy",
16
+		"/containers/foobar/exec",
17
+		"/exec/foobar/start",
18
+	}
19
+
20
+	for _, ep := range endpoints {
21
+		t.Run(ep, func(t *testing.T) {
22
+			t.Parallel()
23
+
24
+			res, body, err := request.Post(ep, request.RawString("{invalid json"), request.JSON)
25
+			assert.NilError(t, err)
26
+			assert.Equal(t, res.StatusCode, http.StatusBadRequest)
27
+
28
+			buf, err := request.ReadBody(body)
29
+			assert.NilError(t, err)
30
+			assert.Check(t, is.Contains(string(buf), "invalid character 'i' looking for beginning of object key string"))
31
+
32
+			res, body, err = request.Post(ep, request.JSON)
33
+			assert.NilError(t, err)
34
+			assert.Equal(t, res.StatusCode, http.StatusBadRequest)
35
+
36
+			buf, err = request.ReadBody(body)
37
+			assert.NilError(t, err)
38
+			assert.Check(t, is.Contains(string(buf), "got EOF while reading request body"))
39
+		})
40
+	}
41
+}
... ...
@@ -3,6 +3,7 @@ package network // import "github.com/docker/docker/integration/network"
3 3
 import (
4 4
 	"bytes"
5 5
 	"context"
6
+	"net/http"
6 7
 	"os/exec"
7 8
 	"strings"
8 9
 	"testing"
... ...
@@ -10,6 +11,7 @@ import (
10 10
 	"github.com/docker/docker/api/types"
11 11
 	"github.com/docker/docker/integration/internal/container"
12 12
 	"github.com/docker/docker/internal/test/daemon"
13
+	"github.com/docker/docker/internal/test/request"
13 14
 	"gotest.tools/assert"
14 15
 	is "gotest.tools/assert/cmp"
15 16
 	"gotest.tools/skip"
... ...
@@ -56,3 +58,35 @@ func TestRunContainerWithBridgeNone(t *testing.T) {
56 56
 	assert.NilError(t, err)
57 57
 	assert.Check(t, is.Equal(stdout.String(), result.Combined()), "The network namspace of container should be the same with host when --net=host and bridge network is disabled")
58 58
 }
59
+
60
+func TestNetworkInvalidJSON(t *testing.T) {
61
+	defer setupTest(t)()
62
+
63
+	endpoints := []string{
64
+		"/networks/create",
65
+		"/networks/bridge/connect",
66
+		"/networks/bridge/disconnect",
67
+	}
68
+
69
+	for _, ep := range endpoints {
70
+		t.Run(ep, func(t *testing.T) {
71
+			t.Parallel()
72
+
73
+			res, body, err := request.Post(ep, request.RawString("{invalid json"), request.JSON)
74
+			assert.NilError(t, err)
75
+			assert.Equal(t, res.StatusCode, http.StatusBadRequest)
76
+
77
+			buf, err := request.ReadBody(body)
78
+			assert.NilError(t, err)
79
+			assert.Check(t, is.Contains(string(buf), "invalid character 'i' looking for beginning of object key string"))
80
+
81
+			res, body, err = request.Post(ep, request.JSON)
82
+			assert.NilError(t, err)
83
+			assert.Equal(t, res.StatusCode, http.StatusBadRequest)
84
+
85
+			buf, err = request.ReadBody(body)
86
+			assert.NilError(t, err)
87
+			assert.Check(t, is.Contains(string(buf), "got EOF while reading request body"))
88
+		})
89
+	}
90
+}
59 91
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+package common // import "github.com/docker/docker/integration/plugin/common"
1
+
2
+import (
3
+	"fmt"
4
+	"os"
5
+	"testing"
6
+
7
+	"github.com/docker/docker/internal/test/environment"
8
+)
9
+
10
+var testEnv *environment.Execution
11
+
12
+func TestMain(m *testing.M) {
13
+	var err error
14
+	testEnv, err = environment.New()
15
+	if err != nil {
16
+		fmt.Println(err)
17
+		os.Exit(1)
18
+	}
19
+	testEnv.Print()
20
+	os.Exit(m.Run())
21
+}
22
+
23
+func setupTest(t *testing.T) func() {
24
+	environment.ProtectAll(t, testEnv)
25
+	return func() { testEnv.Clean(t) }
26
+}
0 27
new file mode 100644
... ...
@@ -0,0 +1,38 @@
0
+package common // import "github.com/docker/docker/integration/plugin/common"
1
+
2
+import (
3
+	"net/http"
4
+	"testing"
5
+
6
+	"github.com/docker/docker/internal/test/request"
7
+	"gotest.tools/assert"
8
+	is "gotest.tools/assert/cmp"
9
+)
10
+
11
+func TestPluginInvalidJSON(t *testing.T) {
12
+	defer setupTest(t)()
13
+
14
+	endpoints := []string{"/plugins/foobar/set"}
15
+
16
+	for _, ep := range endpoints {
17
+		t.Run(ep, func(t *testing.T) {
18
+			t.Parallel()
19
+
20
+			res, body, err := request.Post(ep, request.RawString("{invalid json"), request.JSON)
21
+			assert.NilError(t, err)
22
+			assert.Equal(t, res.StatusCode, http.StatusBadRequest)
23
+
24
+			buf, err := request.ReadBody(body)
25
+			assert.NilError(t, err)
26
+			assert.Check(t, is.Contains(string(buf), "invalid character 'i' looking for beginning of object key string"))
27
+
28
+			res, body, err = request.Post(ep, request.JSON)
29
+			assert.NilError(t, err)
30
+			assert.Equal(t, res.StatusCode, http.StatusBadRequest)
31
+
32
+			buf, err = request.ReadBody(body)
33
+			assert.NilError(t, err)
34
+			assert.Check(t, is.Contains(string(buf), "got EOF while reading request body"))
35
+		})
36
+	}
37
+}
... ...
@@ -2,6 +2,7 @@ package volume
2 2
 
3 3
 import (
4 4
 	"context"
5
+	"net/http"
5 6
 	"path/filepath"
6 7
 	"strings"
7 8
 	"testing"
... ...
@@ -95,6 +96,34 @@ func TestVolumesInspect(t *testing.T) {
95 95
 	assert.Check(t, createdAt.Truncate(time.Minute).Equal(now.Truncate(time.Minute)), "CreatedAt (%s) not equal to creation time (%s)", createdAt, now)
96 96
 }
97 97
 
98
+func TestVolumesInvalidJSON(t *testing.T) {
99
+	defer setupTest(t)()
100
+
101
+	endpoints := []string{"/volumes/create"}
102
+
103
+	for _, ep := range endpoints {
104
+		t.Run(ep, func(t *testing.T) {
105
+			t.Parallel()
106
+
107
+			res, body, err := request.Post(ep, request.RawString("{invalid json"), request.JSON)
108
+			assert.NilError(t, err)
109
+			assert.Equal(t, res.StatusCode, http.StatusBadRequest)
110
+
111
+			buf, err := request.ReadBody(body)
112
+			assert.NilError(t, err)
113
+			assert.Check(t, is.Contains(string(buf), "invalid character 'i' looking for beginning of object key string"))
114
+
115
+			res, body, err = request.Post(ep, request.JSON)
116
+			assert.NilError(t, err)
117
+			assert.Equal(t, res.StatusCode, http.StatusBadRequest)
118
+
119
+			buf, err = request.ReadBody(body)
120
+			assert.NilError(t, err)
121
+			assert.Check(t, is.Contains(string(buf), "got EOF while reading request body"))
122
+		})
123
+	}
124
+}
125
+
98 126
 func getPrefixAndSlashFromDaemonPlatform() (prefix, slash string) {
99 127
 	if testEnv.OSType == "windows" {
100 128
 		return "c:", `\`