Skip to content

Commit 1c28daa

Browse files
authored
fix: enforce S3-compatible bucket naming rules (#18)
1 parent 702ab63 commit 1c28daa

File tree

2 files changed

+57
-2
lines changed

2 files changed

+57
-2
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package handlers
2+
3+
import "testing"
4+
5+
func TestIsValidBucketName(t *testing.T) {
6+
tests := []struct {
7+
name string
8+
input string
9+
expected bool
10+
}{
11+
{name: "valid simple", input: "my-bucket", expected: true},
12+
{name: "valid dotted", input: "my.bucket.name", expected: true},
13+
{name: "too short", input: "ab", expected: false},
14+
{name: "too long", input: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", expected: false},
15+
{name: "uppercase", input: "MyBucket", expected: false},
16+
{name: "underscore", input: "my_bucket", expected: false},
17+
{name: "leading hyphen", input: "-bucket", expected: false},
18+
{name: "trailing hyphen", input: "bucket-", expected: false},
19+
{name: "leading dot", input: ".bucket", expected: false},
20+
{name: "trailing dot", input: "bucket.", expected: false},
21+
{name: "consecutive dots", input: "bucket..name", expected: false},
22+
{name: "dot hyphen pair", input: "bucket.-name", expected: false},
23+
{name: "hyphen dot pair", input: "bucket-.name", expected: false},
24+
{name: "ip format", input: "192.168.1.1", expected: false},
25+
}
26+
27+
for _, tt := range tests {
28+
t.Run(tt.name, func(t *testing.T) {
29+
actual := isValidBucketName(tt.input)
30+
if actual != tt.expected {
31+
t.Fatalf("isValidBucketName(%q) = %v, want %v", tt.input, actual, tt.expected)
32+
}
33+
})
34+
}
35+
}

internal/handlers/buckets_handler.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import (
44
"archive/zip"
55
"fmt"
66
"io"
7+
"net"
78
"net/http"
89
"path/filepath"
10+
"regexp"
911
"strconv"
1012
"strings"
1113
"time"
@@ -26,6 +28,8 @@ type BucketsHandler struct {
2628
minioFactory services.MinioClientFactory
2729
}
2830

31+
var bucketNamePattern = regexp.MustCompile(`^[a-z0-9][a-z0-9.-]*[a-z0-9]$`)
32+
2933
func NewBucketsHandler(minioFactory services.MinioClientFactory) *BucketsHandler {
3034
return &BucketsHandler{minioFactory: minioFactory}
3135
}
@@ -103,9 +107,9 @@ func (h *BucketsHandler) CreateBucket(c echo.Context) error {
103107
"Error": "Bucket name is required",
104108
})
105109
}
106-
if len(bucketName) < 3 || len(bucketName) > 63 {
110+
if !isValidBucketName(bucketName) {
107111
return c.Render(http.StatusBadRequest, "bucket_create_modal", map[string]interface{}{
108-
"Error": "Bucket name must be between 3 and 63 characters",
112+
"Error": "Invalid bucket name",
109113
})
110114
}
111115

@@ -131,6 +135,22 @@ func (h *BucketsHandler) CreateBucket(c echo.Context) error {
131135
return HTMXRedirect(c, "/buckets")
132136
}
133137

138+
func isValidBucketName(name string) bool {
139+
if len(name) < 3 || len(name) > 63 {
140+
return false
141+
}
142+
143+
if !bucketNamePattern.MatchString(name) {
144+
return false
145+
}
146+
147+
if strings.Contains(name, "..") || strings.Contains(name, ".-") || strings.Contains(name, "-.") {
148+
return false
149+
}
150+
151+
return net.ParseIP(name) == nil
152+
}
153+
134154
// DeleteBucket handles removing a bucket
135155
func (h *BucketsHandler) DeleteBucket(c echo.Context) error {
136156
creds, err := GetCredentials(c)

0 commit comments

Comments
 (0)