diff --git a/weed/s3api/s3api_bucket_config.go b/weed/s3api/s3api_bucket_config.go index 9a3a3b02d1..dc13958eca 100644 --- a/weed/s3api/s3api_bucket_config.go +++ b/weed/s3api/s3api_bucket_config.go @@ -646,6 +646,24 @@ func (s3a *S3ApiServer) isVersioningConfigured(bucket string) (bool, error) { return config.Versioning != "" || config.ObjectLockConfig != nil, nil } +// checkVersionIdConfigured reports ErrInvalidArgument when versionId names a +// specific version on a bucket that has never had versioning configured. The +// implicit "null" version of pre-versioning objects stays addressable. +func (s3a *S3ApiServer) checkVersionIdConfigured(bucket, versionId string) s3err.ErrorCode { + if versionId == "" || versionId == "null" { + return s3err.ErrNone + } + configured, err := s3a.isVersioningConfigured(bucket) + if err != nil { + // Missing buckets and lookup failures are answered by the handler path. + return s3err.ErrNone + } + if !configured { + return s3err.ErrInvalidArgument + } + return s3err.ErrNone +} + // isObjectLockEnabled checks if Object Lock is enabled for a bucket (with caching) func (s3a *S3ApiServer) isObjectLockEnabled(bucket string) (bool, error) { config, errCode := s3a.getBucketConfig(bucket) diff --git a/weed/s3api/s3api_object_handlers.go b/weed/s3api/s3api_object_handlers.go index 5ea729fbc2..7b45d186c1 100644 --- a/weed/s3api/s3api_object_handlers.go +++ b/weed/s3api/s3api_object_handlers.go @@ -680,6 +680,15 @@ func (s3a *S3ApiServer) GetObjectHandler(w http.ResponseWriter, r *http.Request) bucket, object, totalTime, conditionalHeadersTime, versioningCheckTime, entryFetchTime, streamTime) }() + // A non-null versionId only has meaning once versioning has been + // configured; reject it before directory or conditional handling can + // answer the request as a plain object. + versionId := r.URL.Query().Get("versionId") + if errCode := s3a.checkVersionIdConfigured(bucket, versionId); errCode != s3err.ErrNone { + s3err.WriteErrorResponse(w, r, errCode) + return + } + // Handle directory objects with shared logic if s3a.handleDirectoryObjectRequest(w, r, bucket, object, "GetObjectHandler") { return // Directory object request was handled @@ -693,9 +702,6 @@ func (s3a *S3ApiServer) GetObjectHandler(w http.ResponseWriter, r *http.Request) return } - // Check for specific version ID in query parameters - versionId := r.URL.Query().Get("versionId") - var ( entry *filer_pb.Entry // Declare entry at function scope for SSE processing targetVersionId string @@ -2359,6 +2365,14 @@ func (s3a *S3ApiServer) HeadObjectHandler(w http.ResponseWriter, r *http.Request return // SOSAPI request was handled } + // Reject named versions before directory or conditional handling can + // answer the request as a plain object. + versionId := r.URL.Query().Get("versionId") + if errCode := s3a.checkVersionIdConfigured(bucket, versionId); errCode != s3err.ErrNone { + s3err.WriteErrorResponse(w, r, errCode) + return + } + // Handle directory objects with shared logic if s3a.handleDirectoryObjectRequest(w, r, bucket, object, "HeadObjectHandler") { return // Directory object request was handled @@ -2370,9 +2384,6 @@ func (s3a *S3ApiServer) HeadObjectHandler(w http.ResponseWriter, r *http.Request return } - // Check for specific version ID in query parameters - versionId := r.URL.Query().Get("versionId") - var ( entry *filer_pb.Entry // Declare entry at function scope for SSE processing targetVersionId string diff --git a/weed/s3api/s3api_object_handlers_delete.go b/weed/s3api/s3api_object_handlers_delete.go index a21a73fe29..850b2ca84a 100644 --- a/weed/s3api/s3api_object_handlers_delete.go +++ b/weed/s3api/s3api_object_handlers_delete.go @@ -232,6 +232,11 @@ func (s3a *S3ApiServer) DeleteObjectHandler(w http.ResponseWriter, r *http.Reque versioningConfigured := (versioningState != "") + if errCode := s3a.checkVersionIdConfigured(bucket, versionId); errCode != s3err.ErrNone { + s3err.WriteErrorResponse(w, r, errCode) + return + } + var auditLog *s3err.AccessLog if s3err.Logger != nil { auditLog = s3err.GetAccessLog(r, http.StatusNoContent, s3err.ErrNone)