fix: bucket name can not contain uppercase letters rustfs/rustfs#2636

This commit is contained in:
马登山
2026-04-22 09:05:49 +08:00
parent d99e02f129
commit 277f7dc7a0
6 changed files with 74 additions and 5 deletions
+10 -4
View File
@@ -4,13 +4,14 @@ import * as React from "react"
import { useTranslation } from "react-i18next"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Field, FieldContent, FieldLabel } from "@/components/ui/field"
import { Field, FieldContent, FieldDescription, FieldLabel } from "@/components/ui/field"
import { Switch } from "@/components/ui/switch"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { useMessage } from "@/lib/feedback/message"
import { useBucket } from "@/hooks/use-bucket"
import { usePermissions } from "@/hooks/use-permissions"
import { getOptionalBucketNameError } from "@/lib/bucket-name"
import { getBytes } from "@/lib/functions"
import { cn } from "@/lib/utils"
@@ -48,9 +49,9 @@ export function BucketNewForm({ show, onShowChange }: BucketNewFormProps) {
const [creating, setCreating] = React.useState(false)
const trimmedBucketName = objectKey.trim()
const showNameError = trimmedBucketName.length > 0 && (trimmedBucketName.length < 3 || trimmedBucketName.length > 63)
const nameError = getOptionalBucketNameError(objectKey)
const parsedRetentionPeriod = Math.max(1, Number.parseInt(retentionPeriod, 10) || 1)
const isSubmitDisabled = creating || trimmedBucketName.length < 3 || trimmedBucketName.length > 63
const isSubmitDisabled = creating || !trimmedBucketName || !!nameError
const canCreateBucket = canCapability("bucket.create")
const bucketContext = trimmedBucketName ? { bucket: trimmedBucketName } : {}
const canEditVersioning = canCapability("bucket.versioning.edit", bucketContext)
@@ -85,6 +86,10 @@ export function BucketNewForm({ show, onShowChange }: BucketNewFormProps) {
const handleCreateBucket = async () => {
if (!canCreateBucket || isSubmitDisabled) return
if (nameError) {
message.error(t(nameError))
return
}
const bucketName = trimmedBucketName
setCreating(true)
@@ -161,9 +166,10 @@ export function BucketNewForm({ show, onShowChange }: BucketNewFormProps) {
value={objectKey}
onChange={(e) => setObjectKey(e.target.value)}
autoComplete="off"
className={cn("w-full", showNameError && "border-destructive focus-visible:ring-destructive")}
className={cn("w-full", nameError && "border-destructive focus-visible:ring-destructive")}
/>
</FieldContent>
{nameError && <FieldDescription className="text-destructive">{t(nameError)}</FieldDescription>}
</Field>
<Field orientation="responsive" className="items-center">
+12 -1
View File
@@ -40,6 +40,7 @@ import {
} from "@aws-sdk/client-s3"
import { useS3 } from "@/contexts/s3-context"
import { useApi } from "@/contexts/api-context"
import { getBucketNameError } from "@/lib/bucket-name"
const BUCKETS_CACHE_DURATION = 10000
let listBucketsCache: ListBucketsCommandOutput | null = null
@@ -97,7 +98,17 @@ export function useBucket() {
const createBucket = useCallback(
async (params: { Bucket: string; ObjectLockEnabledForBucket?: boolean }) => {
const result = await client.send(new CreateBucketCommand(params))
const nameError = getBucketNameError(params.Bucket)
if (nameError) {
throw new Error(nameError)
}
const result = await client.send(
new CreateBucketCommand({
...params,
Bucket: params.Bucket.trim(),
}),
)
invalidateBucketsCache()
return result
},
+2
View File
@@ -92,6 +92,8 @@
"Bucket Policy": "Bucket Policy",
"Bucket Quota": "Bucket Quota",
"Bucket Quota Exceeded": "Bucket Quota Exceeded",
"Bucket names must be 3-63 characters long": "Bucket names must be 3-63 characters long",
"Bucket names must not contain uppercase letters": "Bucket names must not contain uppercase letters",
"Quota Size": "Quota Size",
"Bucket Quota Insufficient": "Bucket Quota Insufficient",
"Quota Warning Content": "The total size of the selected files is {total}, which exceeds the remaining bucket quota {remaining}. Do you want to continue?",
+2
View File
@@ -97,6 +97,8 @@
"Bucket Quota": "存储桶配额",
"Bucket Quota Exceeded": "存储桶配额已超出",
"Bucket Quota Insufficient": "存储桶配额不足",
"Bucket names must be 3-63 characters long": "存储桶名称长度必须为 3-63 个字符",
"Bucket names must not contain uppercase letters": "存储桶名称不能包含大写字母",
"Set Bucket CORS": "设置存储桶跨域配置",
"Bucket Replication": "存储桶复制",
"Bucket Setting": "存储桶设置",
+29
View File
@@ -0,0 +1,29 @@
const BUCKET_NAME_MIN_LENGTH = 3
const BUCKET_NAME_MAX_LENGTH = 63
const UPPERCASE_BUCKET_NAME_PATTERN = /[A-Z]/
export function getBucketNameError(name: string): string | null {
const trimmedName = name.trim()
if (trimmedName.length < BUCKET_NAME_MIN_LENGTH || trimmedName.length > BUCKET_NAME_MAX_LENGTH) {
return "Bucket names must be 3-63 characters long"
}
if (UPPERCASE_BUCKET_NAME_PATTERN.test(trimmedName)) {
return "Bucket names must not contain uppercase letters"
}
return null
}
export function getOptionalBucketNameError(name: string): string | null {
if (!name.trim()) {
return null
}
return getBucketNameError(name)
}
export function isBucketNameValid(name: string): boolean {
return getBucketNameError(name) === null
}
+19
View File
@@ -0,0 +1,19 @@
import test from "node:test"
import assert from "node:assert/strict"
import { getBucketNameError, getOptionalBucketNameError, isBucketNameValid } from "../../lib/bucket-name"
test("isBucketNameValid accepts lowercase bucket names", () => {
assert.equal(isBucketNameValid("bucket-demo"), true)
})
test("getBucketNameError rejects uppercase letters", () => {
assert.equal(getBucketNameError("Bucket-Demo"), "Bucket names must not contain uppercase letters")
})
test("getBucketNameError rejects names shorter than 3 characters", () => {
assert.equal(getBucketNameError("ab"), "Bucket names must be 3-63 characters long")
})
test("getOptionalBucketNameError ignores empty input", () => {
assert.equal(getOptionalBucketNameError(" "), null)
})