From 7eb9713a67a510fb38e283f504566f30663b329a Mon Sep 17 00:00:00 2001
From: Denis Kirillov <denis@nspcc.ru>
Date: Fri, 6 Aug 2021 16:05:57 +0300
Subject: [PATCH] [#189] Add bucket name checking

Signed-off-by: Denis Kirillov <denis@nspcc.ru>
---
 api/handler/put.go      | 40 ++++++++++++++++++++++++++++++++++++
 api/handler/put_test.go | 45 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 85 insertions(+)
 create mode 100644 api/handler/put_test.go

diff --git a/api/handler/put.go b/api/handler/put.go
index 77da94fb..d8e8d19f 100644
--- a/api/handler/put.go
+++ b/api/handler/put.go
@@ -3,6 +3,7 @@ package handler
 import (
 	"encoding/xml"
 	"fmt"
+	"net"
 	"net/http"
 	"strconv"
 	"strings"
@@ -73,6 +74,11 @@ func (h *handler) CreateBucketHandler(w http.ResponseWriter, r *http.Request) {
 		p       = layer.CreateBucketParams{Name: reqInfo.BucketName}
 	)
 
+	if err = checkBucketName(reqInfo.BucketName); err != nil {
+		h.logAndSendError(w, "invalid bucket name", reqInfo, err)
+		return
+	}
+
 	if val, ok := r.Header["X-Amz-Acl"]; ok {
 		p.ACL, err = parseBasicACL(val[0])
 	} else {
@@ -124,6 +130,40 @@ func (h *handler) CreateBucketHandler(w http.ResponseWriter, r *http.Request) {
 	api.WriteSuccessResponseHeadersOnly(w)
 }
 
+func checkBucketName(bucketName string) error {
+	if len(bucketName) < 3 || len(bucketName) > 63 {
+		return api.GetAPIError(api.ErrInvalidBucketName)
+	}
+
+	if strings.HasPrefix(bucketName, "xn--") || strings.HasSuffix(bucketName, "-s3alias") {
+		return api.GetAPIError(api.ErrInvalidBucketName)
+	}
+	if net.ParseIP(bucketName) != nil {
+		return api.GetAPIError(api.ErrInvalidBucketName)
+	}
+
+	labels := strings.Split(bucketName, ".")
+	for _, label := range labels {
+		if len(label) == 0 {
+			return api.GetAPIError(api.ErrInvalidBucketName)
+		}
+		for i, r := range label {
+			if !isAlphaNum(r) && r != '-' {
+				return api.GetAPIError(api.ErrInvalidBucketName)
+			}
+			if (i == 0 || i == len(label)-1) && r == '-' {
+				return api.GetAPIError(api.ErrInvalidBucketName)
+			}
+		}
+	}
+
+	return nil
+}
+
+func isAlphaNum(char int32) bool {
+	return 'a' <= char && char <= 'z' || '0' <= char && char <= '9'
+}
+
 func parseLocationConstraint(r *http.Request) (*createBucketParams, error) {
 	if r.ContentLength == 0 {
 		return new(createBucketParams), nil
diff --git a/api/handler/put_test.go b/api/handler/put_test.go
new file mode 100644
index 00000000..65ea8a07
--- /dev/null
+++ b/api/handler/put_test.go
@@ -0,0 +1,45 @@
+package handler
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestCheckBucketName(t *testing.T) {
+	for _, tc := range []struct {
+		name string
+		err  bool
+	}{
+		{name: "bucket"},
+		{name: "2bucket"},
+		{name: "buc.ket"},
+		{name: "buc-ket"},
+		{name: "abc"},
+		{name: "63aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
+		{name: "buc.-ket", err: true},
+		{name: "bucket.", err: true},
+		{name: ".bucket", err: true},
+		{name: "bucket.", err: true},
+		{name: "bucket-", err: true},
+		{name: "-bucket", err: true},
+		{name: "Bucket", err: true},
+		{name: "buc.-ket", err: true},
+		{name: "buc-.ket", err: true},
+		{name: "Bucket", err: true},
+		{name: "buc!ket", err: true},
+		{name: "buc_ket", err: true},
+		{name: "xn--bucket", err: true},
+		{name: "bucket-s3alias", err: true},
+		{name: "192.168.0.1", err: true},
+		{name: "as", err: true},
+		{name: "64aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", err: true},
+	} {
+		err := checkBucketName(tc.name)
+		if tc.err {
+			require.Error(t, err, "bucket name: %s", tc.name)
+		} else {
+			require.NoError(t, err, "bucket name: %s", tc.name)
+		}
+	}
+}