diff --git a/cmd/api-errors.go b/cmd/api-errors.go index b57f69de1..394ca115e 100644 --- a/cmd/api-errors.go +++ b/cmd/api-errors.go @@ -139,6 +139,7 @@ const ( ErrSlowDown ErrInvalidPrefixMarker ErrBadRequest + ErrKeyTooLongError // Add new error codes here. // SSE-S3 related API errors @@ -187,6 +188,7 @@ const ( ErrRequestBodyParse ErrObjectExistsAsDirectory ErrInvalidObjectName + ErrInvalidObjectNamePrefixSlash ErrInvalidResourceName ErrServerNotInitialized ErrOperationTimedOut @@ -682,6 +684,11 @@ var errorCodes = errorCodeMap{ Description: "400 BadRequest", HTTPStatusCode: http.StatusBadRequest, }, + ErrKeyTooLongError: { + Code: "KeyTooLongError", + Description: "Your key is too long", + HTTPStatusCode: http.StatusBadRequest, + }, // FIXME: Actual XML error response also contains the header which missed in list of signed header parameters. ErrUnsignedHeaders: { @@ -885,6 +892,11 @@ var errorCodes = errorCodeMap{ Description: "Object name contains unsupported characters.", HTTPStatusCode: http.StatusBadRequest, }, + ErrInvalidObjectNamePrefixSlash: { + Code: "XMinioInvalidObjectName", + Description: "Object name contains a leading slash.", + HTTPStatusCode: http.StatusBadRequest, + }, ErrInvalidResourceName: { Code: "XMinioInvalidResourceName", Description: "Resource name contains bad components such as \"..\" or \".\".", @@ -1579,6 +1591,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) { apiErr = ErrMethodNotAllowed case ObjectNameInvalid: apiErr = ErrInvalidObjectName + case ObjectNamePrefixAsSlash: + apiErr = ErrInvalidObjectNamePrefixSlash case InvalidUploadID: apiErr = ErrNoSuchUpload case InvalidPart: @@ -1639,6 +1653,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) { apiErr = ErrBackendDown case crypto.Error: apiErr = ErrObjectTampered + case ObjectNameTooLong: + apiErr = ErrKeyTooLongError default: var ie, iw int // This work-around is to handle the issue golang/go#30648 diff --git a/cmd/object-api-errors.go b/cmd/object-api-errors.go index 06ad8518a..1d38ea250 100644 --- a/cmd/object-api-errors.go +++ b/cmd/object-api-errors.go @@ -269,11 +269,27 @@ func (e BucketNameInvalid) Error() string { // ObjectNameInvalid - object name provided is invalid. type ObjectNameInvalid GenericError +// ObjectNameTooLong - object name too long. +type ObjectNameTooLong GenericError + +// ObjectNamePrefixAsSlash - object name has a slash as prefix. +type ObjectNamePrefixAsSlash GenericError + // Return string an error formatted as the given text. func (e ObjectNameInvalid) Error() string { return "Object name invalid: " + e.Bucket + "#" + e.Object } +// Return string an error formatted as the given text. +func (e ObjectNameTooLong) Error() string { + return "Object name too long: " + e.Bucket + "#" + e.Object +} + +// Return string an error formatted as the given text. +func (e ObjectNamePrefixAsSlash) Error() string { + return "Object name contains forward slash as pefix: " + e.Bucket + "#" + e.Object +} + // AllAccessDisabled All access to this object has been disabled type AllAccessDisabled GenericError diff --git a/cmd/object-api-input-checks.go b/cmd/object-api-input-checks.go index 8342bf0ee..09bb7afc4 100644 --- a/cmd/object-api-input-checks.go +++ b/cmd/object-api-input-checks.go @@ -166,18 +166,18 @@ func checkObjectArgs(ctx context.Context, bucket, object string, obj ObjectLayer if err := checkBucketExist(ctx, bucket, obj); err != nil { return err } + + if err := checkObjectNameForLengthAndSlash(bucket, object); err != nil { + return err + } // Validates object name validity after bucket exists. if !IsValidObjectName(object) { - logger.LogIf(ctx, ObjectNameInvalid{ - Bucket: bucket, - Object: object, - }) - return ObjectNameInvalid{ Bucket: bucket, Object: object, } } + return nil } @@ -192,8 +192,10 @@ func checkPutObjectArgs(ctx context.Context, bucket, object string, obj ObjectLa return err } + if err := checkObjectNameForLengthAndSlash(bucket, object); err != nil { + return err + } if len(object) == 0 || - hasPrefix(object, slashSeparator) || (hasSuffix(object, slashSeparator) && size != 0) || !IsValidObjectPrefix(object) { return ObjectNameInvalid{ diff --git a/cmd/object-api-utils.go b/cmd/object-api-utils.go index bbbcf0967..7810b6721 100644 --- a/cmd/object-api-utils.go +++ b/cmd/object-api-utils.go @@ -136,7 +136,7 @@ func IsValidObjectName(object string) bool { if len(object) == 0 { return false } - if hasSuffix(object, slashSeparator) || hasPrefix(object, slashSeparator) { + if hasSuffix(object, slashSeparator) { return false } return IsValidObjectPrefix(object) @@ -148,9 +148,6 @@ func IsValidObjectPrefix(object string) bool { if hasBadPathComponent(object) { return false } - if len(object) > 1024 { - return false - } if !utf8.ValidString(object) { return false } @@ -161,6 +158,25 @@ func IsValidObjectPrefix(object string) bool { return true } +// checkObjectNameForLengthAndSlash -check for the validity of object name length and prefis as slash +func checkObjectNameForLengthAndSlash(bucket, object string) error { + // Check for the length of object name + if len(object) > 1024 { + return ObjectNameTooLong{ + Bucket: bucket, + Object: object, + } + } + // Check for slash as prefix in object name + if hasPrefix(object, slashSeparator) { + return ObjectNamePrefixAsSlash{ + Bucket: bucket, + Object: object, + } + } + return nil +} + // Slash separator. const slashSeparator = "/" diff --git a/cmd/object-api-utils_test.go b/cmd/object-api-utils_test.go index 1acdeca30..b9a9f5a3c 100644 --- a/cmd/object-api-utils_test.go +++ b/cmd/object-api-utils_test.go @@ -113,7 +113,6 @@ func TestIsValidObjectName(t *testing.T) { // passing invalid object names. {"", false}, {"a/b/c/", false}, - {"/a/b/c", false}, {"../../etc", false}, {"../../", false}, {"/../../etc", false}, diff --git a/cmd/server_test.go b/cmd/server_test.go index 515c0157d..7a8e7a2f5 100644 --- a/cmd/server_test.go +++ b/cmd/server_test.go @@ -1354,7 +1354,37 @@ func (s *TestSuiteCommon) TestPutObjectLongName(c *check) { response, err = client.Do(request) c.Assert(err, nil) c.Assert(response.StatusCode, http.StatusOK) - // make long object name. + + //make long object name. + longObjName = fmt.Sprintf("%0255d/%0255d/%0255d/%0255d/%0255d", 1, 1, 1, 1, 1) + if IsDocker() || IsKubernetes() { + longObjName = fmt.Sprintf("%0242d/%0242d/%0242d/%0242d/%0242d", 1, 1, 1, 1, 1) + } + // create new HTTP request to insert the object. + buffer = bytes.NewReader([]byte("hello world")) + request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, longObjName), + int64(buffer.Len()), buffer, s.accessKey, s.secretKey, s.signer) + c.Assert(err, nil) + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, nil) + c.Assert(response.StatusCode, http.StatusBadRequest) + verifyError(c, response, "KeyTooLongError", "Your key is too long", http.StatusBadRequest) + + // make object name with prefix as slash + longObjName = fmt.Sprintf("/%0255d/%0255d", 1, 1) + buffer = bytes.NewReader([]byte("hello world")) + // create new HTTP request to insert the object. + request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, longObjName), + int64(buffer.Len()), buffer, s.accessKey, s.secretKey, s.signer) + c.Assert(err, nil) + // execute the HTTP request. + response, err = client.Do(request) + c.Assert(err, nil) + c.Assert(response.StatusCode, http.StatusBadRequest) + verifyError(c, response, "XMinioInvalidObjectName", "Object name contains a leading slash.", http.StatusBadRequest) + + //make object name as unsuported longObjName = fmt.Sprintf("%0256d", 1) buffer = bytes.NewReader([]byte("hello world")) request, err = newTestSignedRequest("PUT", getPutObjectURL(s.endPoint, bucketName, longObjName),