// Copyright (c) 2015-2021 MinIO, Inc. // // This file is part of MinIO Object Storage stack // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. package cmd import ( "bytes" "encoding/base64" "encoding/binary" "hash/crc32" "net/http" "net/http/httptest" "strings" "testing" "github.com/minio/minio/internal/auth" ) func TestPutBucketCorsWireValidation(t *testing.T) { ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{ t: t, objAPITest: testPutBucketCorsWireValidation, endpoints: []string{"PutBucketCors"}, }) } func testPutBucketCorsWireValidation(_ ObjectLayer, _ string, bucketName string, apiRouter http.Handler, creds auth.Credentials, t *testing.T, ) { valid := `*GET` rule := `*GET` tests := []struct { name string body string want int wantCode string }{ { name: "second XML root", body: valid + ``, want: http.StatusBadRequest, wantCode: "MalformedXML", }, { name: "255 Unicode character ID", body: `` + strings.Repeat("界", 255) + `*GET`, want: http.StatusOK, }, { name: "256 Unicode character ID", body: `` + strings.Repeat("界", 256) + `*GET`, want: http.StatusBadRequest, wantCode: "MalformedXML", }, { name: "lowercase method", body: `*get`, want: http.StatusBadRequest, wantCode: "MalformedXML", }, { name: "empty origin", body: `GET`, want: http.StatusBadRequest, wantCode: "MalformedXML", }, { name: "question mark origin wildcard", body: `https://?.example.comGET`, want: http.StatusBadRequest, wantCode: "MalformedXML", }, { name: "question mark header wildcard", body: `*GETx-amz-?`, want: http.StatusBadRequest, wantCode: "MalformedXML", }, { name: "unknown element", body: `*GET`, want: http.StatusBadRequest, wantCode: "MalformedXML", }, { name: "empty max age", body: `*GET`, want: http.StatusBadRequest, wantCode: "MalformedXML", }, { name: "zero max age", body: `*GET0`, want: http.StatusOK, }, { name: "max age int32 overflow", body: `*GET2147483648`, want: http.StatusBadRequest, wantCode: "MalformedXML", }, { name: "100 rules", body: `` + strings.Repeat(rule, 100) + ``, want: http.StatusOK, }, { name: "101 rules", body: `` + strings.Repeat(rule, 101) + ``, want: http.StatusBadRequest, wantCode: "MalformedXML", }, { name: "exactly 64 KiB", body: sizedCORSConfig(maxBucketCorsSize), want: http.StatusOK, }, { name: "over 64 KiB", body: sizedCORSConfig(maxBucketCorsSize + 1), want: http.StatusBadRequest, wantCode: "EntityTooLarge", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req, err := newTestSignedRequestV4(http.MethodPut, getBucketCorsURL("", bucketName), int64(len(tt.body)), bytes.NewReader([]byte(tt.body)), creds.AccessKey, creds.SecretKey, nil) if err != nil { t.Fatal(err) } rec := httptest.NewRecorder() apiRouter.ServeHTTP(rec, req) if rec.Code != tt.want { t.Fatalf("expected status %d, got %d: %s", tt.want, rec.Code, rec.Body.String()) } if tt.wantCode != "" && !bytes.Contains(rec.Body.Bytes(), []byte(""+tt.wantCode+"")) { t.Fatalf("expected error code %s, got: %s", tt.wantCode, rec.Body.String()) } }) } } func sizedCORSConfig(size int) string { prefix := `*GET` suffix := `` return prefix + strings.Repeat(" ", size-len(prefix)-len(suffix)) + suffix } func TestPutBucketCorsChecksumValidation(t *testing.T) { ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{ t: t, objAPITest: testPutBucketCorsChecksumValidation, endpoints: []string{"PutBucketCors"}, }) } func testPutBucketCorsChecksumValidation(_ ObjectLayer, _ string, bucketName string, apiRouter http.Handler, creds auth.Credentials, t *testing.T, ) { body := []byte(`*GET`) tests := []struct { name string configure func(*http.Request) want int wantCode string }{ { name: "missing checksum", configure: func(req *http.Request) { req.Header.Del("Content-Md5") }, want: http.StatusBadRequest, wantCode: "MissingContentMD5", }, { name: "bad content md5", configure: func(req *http.Request) { req.Header.Set("Content-Md5", getMD5HashBase64([]byte("different body"))) }, want: http.StatusBadRequest, wantCode: "BadDigest", }, { name: "valid sdk crc32", configure: func(req *http.Request) { req.Header.Del("Content-Md5") req.Header.Set("X-Amz-Sdk-Checksum-Algorithm", "CRC32") req.Header.Set("X-Amz-Checksum-Crc32", corsCRC32Base64(body)) }, want: http.StatusOK, }, { name: "bad sdk crc32", configure: func(req *http.Request) { req.Header.Del("Content-Md5") req.Header.Set("X-Amz-Sdk-Checksum-Algorithm", "CRC32") req.Header.Set("X-Amz-Checksum-Crc32", corsCRC32Base64([]byte("different body"))) }, want: http.StatusBadRequest, wantCode: "BadDigest", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req, err := newTestRequest(http.MethodPut, getBucketCorsURL("", bucketName), int64(len(body)), bytes.NewReader(body)) if err != nil { t.Fatal(err) } tt.configure(req) if err = signRequestV4(req, creds.AccessKey, creds.SecretKey); err != nil { t.Fatal(err) } rec := httptest.NewRecorder() apiRouter.ServeHTTP(rec, req) if rec.Code != tt.want || (tt.wantCode != "" && !bytes.Contains(rec.Body.Bytes(), []byte(""+tt.wantCode+""))) { t.Fatalf("expected status %d and code %s, got %d: %s", tt.want, tt.wantCode, rec.Code, rec.Body.String()) } }) } } func corsCRC32Base64(data []byte) string { var checksum [4]byte binary.BigEndian.PutUint32(checksum[:], crc32.ChecksumIEEE(data)) return base64.StdEncoding.EncodeToString(checksum[:]) }